@devrev-computer/skills 1.0.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 (179) hide show
  1. package/README.md +37 -0
  2. package/bin/install.mjs +158 -0
  3. package/package.json +33 -0
  4. package/skills/account-evaluation/account-evaluation.md +64 -0
  5. package/skills/account-research/account-research.md +323 -0
  6. package/skills/account-research/references/signals-guide.md +52 -0
  7. package/skills/create-workflow-template/create-workflow-template.md +1091 -0
  8. package/skills/create-workflow-template/examples/3592-Generate rca from pia-template.json +1 -0
  9. package/skills/create-workflow-template/examples/4392-Async opportunity review agent-template.json +1 -0
  10. package/skills/create-workflow-template/examples/4441-Ticket escalator from customer message-template.json +1 -0
  11. package/skills/create-workflow-template/examples/4505-Auto-update issue tcd as end of sprint date-template.json +1 -0
  12. package/skills/create-workflow-template/examples/5040-Devrevu - enablement journey - poc emails-template.json +1 -0
  13. package/skills/create-workflow-template/examples/5158-Devrevu - enablement journey - mailing for non enablement journey users-template.json +1 -0
  14. package/skills/create-workflow-template/examples/5216-Account segment missing notification-template.json +1 -0
  15. package/skills/create-workflow-template/examples/working-csat-score-on-ticket-resolved.json +1 -0
  16. package/skills/create-workflow-template/examples/working-enhancement-replace-agent.json +1 -0
  17. package/skills/create-workflow-template/examples/working-invoke-code-sample.json +1 -0
  18. package/skills/create-workflow-template/examples/working-loop-variable-sample.json +1 -0
  19. package/skills/create-workflow-template/operations/actions.md +2919 -0
  20. package/skills/create-workflow-template/operations/blockings.md +38 -0
  21. package/skills/create-workflow-template/operations/controls.md +108 -0
  22. package/skills/create-workflow-template/operations/schema-index.md +166 -0
  23. package/skills/create-workflow-template/operations/schemas/account_created.md +58 -0
  24. package/skills/create-workflow-template/operations/schemas/account_updated.md +73 -0
  25. package/skills/create-workflow-template/operations/schemas/add_comment.md +29 -0
  26. package/skills/create-workflow-template/operations/schemas/airdrop_sync_run_started.md +33 -0
  27. package/skills/create-workflow-template/operations/schemas/airdrop_sync_run_status_updated.md +35 -0
  28. package/skills/create-workflow-template/operations/schemas/article_created.md +96 -0
  29. package/skills/create-workflow-template/operations/schemas/article_updated.md +135 -0
  30. package/skills/create-workflow-template/operations/schemas/ask_ai.md +11 -0
  31. package/skills/create-workflow-template/operations/schemas/classify_object.md +22 -0
  32. package/skills/create-workflow-template/operations/schemas/contact_created.md +43 -0
  33. package/skills/create-workflow-template/operations/schemas/contact_updated.md +65 -0
  34. package/skills/create-workflow-template/operations/schemas/conversation_created.md +108 -0
  35. package/skills/create-workflow-template/operations/schemas/conversation_sla_tracker_updated.md +46 -0
  36. package/skills/create-workflow-template/operations/schemas/conversation_updated.md +130 -0
  37. package/skills/create-workflow-template/operations/schemas/convert_conversation_to_ticket.md +13 -0
  38. package/skills/create-workflow-template/operations/schemas/create_account.md +62 -0
  39. package/skills/create-workflow-template/operations/schemas/create_article.md +79 -0
  40. package/skills/create-workflow-template/operations/schemas/create_brand.md +42 -0
  41. package/skills/create-workflow-template/operations/schemas/create_contact.md +65 -0
  42. package/skills/create-workflow-template/operations/schemas/create_dm.md +53 -0
  43. package/skills/create-workflow-template/operations/schemas/create_enhancement.md +63 -0
  44. package/skills/create-workflow-template/operations/schemas/create_incident.md +136 -0
  45. package/skills/create-workflow-template/operations/schemas/create_issue.md +150 -0
  46. package/skills/create-workflow-template/operations/schemas/create_meeting.md +105 -0
  47. package/skills/create-workflow-template/operations/schemas/create_opportunity.md +123 -0
  48. package/skills/create-workflow-template/operations/schemas/create_ticket.md +184 -0
  49. package/skills/create-workflow-template/operations/schemas/csat_response_received.md +73 -0
  50. package/skills/create-workflow-template/operations/schemas/dev_user_created.md +54 -0
  51. package/skills/create-workflow-template/operations/schemas/dev_user_updated.md +99 -0
  52. package/skills/create-workflow-template/operations/schemas/enhancement_created.md +46 -0
  53. package/skills/create-workflow-template/operations/schemas/enhancement_updated.md +89 -0
  54. package/skills/create-workflow-template/operations/schemas/evaluate_sentiment.md +14 -0
  55. package/skills/create-workflow-template/operations/schemas/execute_metric_action.md +11 -0
  56. package/skills/create-workflow-template/operations/schemas/feature_created.md +40 -0
  57. package/skills/create-workflow-template/operations/schemas/for_each.md +45 -0
  58. package/skills/create-workflow-template/operations/schemas/get_account.md +59 -0
  59. package/skills/create-workflow-template/operations/schemas/get_airdrop_sync_unit.md +32 -0
  60. package/skills/create-workflow-template/operations/schemas/get_brand.md +40 -0
  61. package/skills/create-workflow-template/operations/schemas/get_complete_enhancement_details.md +13 -0
  62. package/skills/create-workflow-template/operations/schemas/get_conversation.md +120 -0
  63. package/skills/create-workflow-template/operations/schemas/get_customer.md +60 -0
  64. package/skills/create-workflow-template/operations/schemas/get_enhancement.md +66 -0
  65. package/skills/create-workflow-template/operations/schemas/get_feature.md +56 -0
  66. package/skills/create-workflow-template/operations/schemas/get_incident.md +85 -0
  67. package/skills/create-workflow-template/operations/schemas/get_issue.md +117 -0
  68. package/skills/create-workflow-template/operations/schemas/get_kg_schema.md +23 -0
  69. package/skills/create-workflow-template/operations/schemas/get_meeting.md +87 -0
  70. package/skills/create-workflow-template/operations/schemas/get_metric_trackers.md +20 -0
  71. package/skills/create-workflow-template/operations/schemas/get_node_schema.md +29 -0
  72. package/skills/create-workflow-template/operations/schemas/get_opportunity.md +93 -0
  73. package/skills/create-workflow-template/operations/schemas/get_org_user.md +57 -0
  74. package/skills/create-workflow-template/operations/schemas/get_org_user_preference.md +40 -0
  75. package/skills/create-workflow-template/operations/schemas/get_part.md +55 -0
  76. package/skills/create-workflow-template/operations/schemas/get_self.md +54 -0
  77. package/skills/create-workflow-template/operations/schemas/get_session_details.md +45 -0
  78. package/skills/create-workflow-template/operations/schemas/get_sprint_board.md +103 -0
  79. package/skills/create-workflow-template/operations/schemas/get_ticket.md +136 -0
  80. package/skills/create-workflow-template/operations/schemas/get_workspace.md +21 -0
  81. package/skills/create-workflow-template/operations/schemas/go_back.md +13 -0
  82. package/skills/create-workflow-template/operations/schemas/http.md +38 -0
  83. package/skills/create-workflow-template/operations/schemas/hybrid_search.md +144 -0
  84. package/skills/create-workflow-template/operations/schemas/if_else.md +16 -0
  85. package/skills/create-workflow-template/operations/schemas/incident_created.md +88 -0
  86. package/skills/create-workflow-template/operations/schemas/incident_updated.md +126 -0
  87. package/skills/create-workflow-template/operations/schemas/init_variable.md +67 -0
  88. package/skills/create-workflow-template/operations/schemas/invoice_created.md +21 -0
  89. package/skills/create-workflow-template/operations/schemas/invoice_updated.md +41 -0
  90. package/skills/create-workflow-template/operations/schemas/invoke_code.md +132 -0
  91. package/skills/create-workflow-template/operations/schemas/issue_created.md +105 -0
  92. package/skills/create-workflow-template/operations/schemas/issue_sla_tracker_updated.md +46 -0
  93. package/skills/create-workflow-template/operations/schemas/issue_updated.md +172 -0
  94. package/skills/create-workflow-template/operations/schemas/link_incident_with_issue.md +14 -0
  95. package/skills/create-workflow-template/operations/schemas/link_ticket_with_issue.md +14 -0
  96. package/skills/create-workflow-template/operations/schemas/list_enhancements.md +74 -0
  97. package/skills/create-workflow-template/operations/schemas/list_issues.md +108 -0
  98. package/skills/create-workflow-template/operations/schemas/list_sessions.md +79 -0
  99. package/skills/create-workflow-template/operations/schemas/list_sprint.md +29 -0
  100. package/skills/create-workflow-template/operations/schemas/list_web_sessions.md +87 -0
  101. package/skills/create-workflow-template/operations/schemas/loop_over_accounts.md +106 -0
  102. package/skills/create-workflow-template/operations/schemas/loop_over_articles.md +126 -0
  103. package/skills/create-workflow-template/operations/schemas/loop_over_customers.md +88 -0
  104. package/skills/create-workflow-template/operations/schemas/loop_over_dev_users.md +75 -0
  105. package/skills/create-workflow-template/operations/schemas/loop_over_enhancements.md +112 -0
  106. package/skills/create-workflow-template/operations/schemas/loop_over_incidents.md +113 -0
  107. package/skills/create-workflow-template/operations/schemas/loop_over_issues.md +217 -0
  108. package/skills/create-workflow-template/operations/schemas/loop_over_meetings.md +150 -0
  109. package/skills/create-workflow-template/operations/schemas/loop_over_opportunity.md +161 -0
  110. package/skills/create-workflow-template/operations/schemas/loop_over_sprints.md +50 -0
  111. package/skills/create-workflow-template/operations/schemas/loop_over_tickets.md +203 -0
  112. package/skills/create-workflow-template/operations/schemas/manual_trigger.md +11 -0
  113. package/skills/create-workflow-template/operations/schemas/meeting_created.md +116 -0
  114. package/skills/create-workflow-template/operations/schemas/meeting_updated.md +152 -0
  115. package/skills/create-workflow-template/operations/schemas/oasis_sql_execute.md +11 -0
  116. package/skills/create-workflow-template/operations/schemas/opportunity_created.md +92 -0
  117. package/skills/create-workflow-template/operations/schemas/opportunity_updated.md +124 -0
  118. package/skills/create-workflow-template/operations/schemas/pick_user.md +16 -0
  119. package/skills/create-workflow-template/operations/schemas/question_answer_created.md +44 -0
  120. package/skills/create-workflow-template/operations/schemas/question_answer_updated.md +75 -0
  121. package/skills/create-workflow-template/operations/schemas/recall_chats.md +13 -0
  122. package/skills/create-workflow-template/operations/schemas/router.md +15 -0
  123. package/skills/create-workflow-template/operations/schemas/send_notification.md +19 -0
  124. package/skills/create-workflow-template/operations/schemas/set_variable.md +67 -0
  125. package/skills/create-workflow-template/operations/schemas/sleep_for.md +12 -0
  126. package/skills/create-workflow-template/operations/schemas/sleep_until.md +17 -0
  127. package/skills/create-workflow-template/operations/schemas/sprint_updated.md +37 -0
  128. package/skills/create-workflow-template/operations/schemas/suggest_part.md +14 -0
  129. package/skills/create-workflow-template/operations/schemas/task_updated.md +79 -0
  130. package/skills/create-workflow-template/operations/schemas/test_example.md +16 -0
  131. package/skills/create-workflow-template/operations/schemas/ticket_created.md +136 -0
  132. package/skills/create-workflow-template/operations/schemas/ticket_sla_tracker_updated.md +46 -0
  133. package/skills/create-workflow-template/operations/schemas/ticket_updated.md +198 -0
  134. package/skills/create-workflow-template/operations/schemas/timeline_comment_created.md +70 -0
  135. package/skills/create-workflow-template/operations/schemas/update_account.md +68 -0
  136. package/skills/create-workflow-template/operations/schemas/update_article.md +95 -0
  137. package/skills/create-workflow-template/operations/schemas/update_brand.md +44 -0
  138. package/skills/create-workflow-template/operations/schemas/update_contact.md +53 -0
  139. package/skills/create-workflow-template/operations/schemas/update_conversation.md +149 -0
  140. package/skills/create-workflow-template/operations/schemas/update_enhancement.md +64 -0
  141. package/skills/create-workflow-template/operations/schemas/update_incident.md +156 -0
  142. package/skills/create-workflow-template/operations/schemas/update_issue.md +173 -0
  143. package/skills/create-workflow-template/operations/schemas/update_meeting.md +114 -0
  144. package/skills/create-workflow-template/operations/schemas/update_opportunity.md +137 -0
  145. package/skills/create-workflow-template/operations/schemas/update_question_answer.md +60 -0
  146. package/skills/create-workflow-template/operations/schemas/update_ticket.md +188 -0
  147. package/skills/create-workflow-template/operations/schemas/watch_ticket_for_updates.md +225 -0
  148. package/skills/create-workflow-template/operations/schemas/web_search.md +17 -0
  149. package/skills/create-workflow-template/operations/schemas/while.md +24 -0
  150. package/skills/create-workflow-template/operations/schemas/widget_created.md +75 -0
  151. package/skills/create-workflow-template/operations/schemas/widget_updated.md +98 -0
  152. package/skills/create-workflow-template/operations/schemas/workspace_created.md +20 -0
  153. package/skills/create-workflow-template/operations/triggers.md +1583 -0
  154. package/skills/customer-brief/customer-brief.md +66 -0
  155. package/skills/deal-review-meddpicc/deal-review-meddpicc.md +58 -0
  156. package/skills/next-step-for-opportunity/next-step-for-opportunity.md +55 -0
  157. package/skills/opportunity-feature-prioritizer/SKILL.md +183 -0
  158. package/skills/sales-call-plan-coach/sales-call-plan-coach.md +73 -0
  159. package/skills/sales-context/sales-context.md +44 -0
  160. package/skills/sales-search-and-lookup/sales-search-and-lookup.md +58 -0
  161. package/skills/skill-creator/SKILL.md +570 -0
  162. package/skills/skill-creator/agents/analyzer.md +274 -0
  163. package/skills/skill-creator/agents/comparator.md +202 -0
  164. package/skills/skill-creator/agents/grader.md +223 -0
  165. package/skills/skill-creator/assets/eval_review.html +146 -0
  166. package/skills/skill-creator/eval-viewer/generate_review.py +471 -0
  167. package/skills/skill-creator/eval-viewer/viewer.html +1325 -0
  168. package/skills/skill-creator/references/schemas.md +430 -0
  169. package/skills/skill-creator/references/tool-patterns.md +290 -0
  170. package/skills/skill-creator/scripts/__init__.py +0 -0
  171. package/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
  172. package/skills/skill-creator/scripts/generate_report.py +326 -0
  173. package/skills/skill-creator/scripts/improve_description.py +247 -0
  174. package/skills/skill-creator/scripts/package_skill.py +136 -0
  175. package/skills/skill-creator/scripts/quick_validate.py +103 -0
  176. package/skills/skill-creator/scripts/run_eval.py +310 -0
  177. package/skills/skill-creator/scripts/run_loop.py +328 -0
  178. package/skills/skill-creator/scripts/utils.py +47 -0
  179. package/skills/trace-diagnosis/trace-diagnosis.md +186 -0
@@ -0,0 +1,1091 @@
1
+ ---
2
+ skill-name: create-workflow-template
3
+ user-invocable: true
4
+ description: Create a DevRev workflow template JSON from a natural language description
5
+ arguments:
6
+ - name: description
7
+ description: Natural language description of the workflow to create
8
+ required: true
9
+ ---
10
+
11
+ # Create DevRev Workflow Template
12
+
13
+ Generate a valid DevRev workflow template JSON. Study the example templates in `.claude/skills/create-workflow-template/examples/` before generating to ensure the output matches the production format.
14
+
15
+ ## Workflow Process
16
+
17
+ 1. **Understand the user's intent** — Parse the natural language description to identify the trigger event, the sequence of actions, any conditions/branching, and any loops.
18
+ 2. **Look up operations** — Consult the operations reference docs in `.claude/skills/create-workflow-template/operations/` to find the correct slugs, namespaces, and input/output schemas for each operation needed. For detailed field-level schemas (input/output ports, field types, required fields), check `.claude/skills/create-workflow-template/operations/schemas/<slug>.md` — the schema index is at `.claude/skills/create-workflow-template/operations/schema-index.md`.
19
+ 3. **Study example templates** — Read relevant templates from `.claude/skills/create-workflow-template/examples/` to see real-world patterns for the operations you need. **Prioritize `working-*.json` examples** — these are validated, import-tested templates. Use python3 to pretty-print: `python3 -c "import json; d=json.load(open('.claude/skills/create-workflow-template/examples/FILE.json')); inner=json.loads(d['data']); print(json.dumps(inner, indent=2))"`.
20
+ 4. **Build the template JSON** — Assemble the complete workflow JSON following the specification below, matching the format used in the example templates.
21
+ 5. **Output the JSON** — Write the template to a file in `templates/<workflow_name>.json` and also display a summary to the user.
22
+ 6. **Save as example (self-improvement)** — After a template is confirmed working (user reports successful import or no errors), copy it to `.claude/skills/create-workflow-template/examples/working-<descriptive-name>.json`. This grows the example library over time, making the skill better with every successful workflow. Also update the example templates table in this file with the new entry.
23
+
24
+ ## Template Wrapper Format
25
+
26
+ Templates are wrapped in an outer envelope. The `data` field is a **stringified JSON** of the inner workflow:
27
+
28
+ ```json
29
+ {
30
+ "templateVersion": "2.0.0",
31
+ "data": "<stringified inner workflow JSON>"
32
+ }
33
+ ```
34
+
35
+ To produce this, build the inner workflow object first, then `JSON.stringify()` it into the `data` field.
36
+
37
+ ## Inner Workflow Structure
38
+
39
+ ```json
40
+ {
41
+ "serialization_version": { "major": 2, "minor": 0, "patch": 0 },
42
+ "title": "Workflow Title (1-256 chars)",
43
+ "description": "What this workflow does (up to 65536 chars)",
44
+ "labels": ["optional-label"],
45
+ "steps": [ ...step objects... ]
46
+ }
47
+ ```
48
+
49
+ | Field | Type | Required | Description |
50
+ |-------|------|----------|-------------|
51
+ | `serialization_version` | object | Yes | Always `{"major": 2, "minor": 0, "patch": 0}` |
52
+ | `title` | string | Yes | 1-256 chars |
53
+ | `description` | string | No | Up to 65536 chars |
54
+ | `labels` | string[] | No | Up to 16 labels, each up to 64 chars. Use `["agent_interaction"]` for agent workflows |
55
+ | `steps` | array | Yes | All workflow steps (triggers + actions + controls + blockings) |
56
+
57
+ ## Step Structure
58
+
59
+ Each entry in `steps` is a node in the workflow graph:
60
+
61
+ ```json
62
+ {
63
+ "name": "Step Display Name",
64
+ "description": "What this step does",
65
+ "reference_key": "unique_step_key",
66
+ "operation": {
67
+ "namespace": "devrev",
68
+ "slug": "operation_slug"
69
+ },
70
+ "input_values": [ ...input value mappings... ],
71
+ "next_steps": [ ...edges to next steps... ],
72
+ "ui_metadata": {
73
+ "position": { "x": 400, "y": 200 }
74
+ },
75
+ "block_step_reference_key": "parent_loop_ref_key"
76
+ }
77
+ ```
78
+
79
+ | Field | Type | Required | Description |
80
+ |-------|------|----------|-------------|
81
+ | `name` | string | Yes | 1-256 chars |
82
+ | `description` | string | No | Up to 65536 chars |
83
+ | `reference_key` | string | Yes | Unique within workflow. Pattern: `^[a-zA-Z_][a-zA-Z0-9_]*$` |
84
+ | `operation` | object | Yes | `{namespace, slug}` — identifies the operation |
85
+ | `input_values` | array | No | Input data/expressions for this step |
86
+ | `next_steps` | array | No | Outgoing edges to other steps |
87
+ | `ui_metadata` | object | No | UI position `{position: {x, y}}` |
88
+ | `block_step_reference_key` | string | No | Ref key of parent loop/block step (for nested steps) |
89
+ | `system_options` | object | No | Execution options (e.g. `execute_as_user`) |
90
+ | `scopes` | object | No | Access scopes |
91
+
92
+ ### Port Schema Rules (`input_ports` / `output_ports`)
93
+
94
+ The UI needs port schemas to render step configuration panels. Rules by operation type:
95
+
96
+ | Operation Type | `input_ports` | `output_ports` | Notes |
97
+ |---------------|:---:|:---:|-------|
98
+ | **Triggers** (`*_created`, `*_updated`) | No | No | System resolves schemas automatically |
99
+ | **Timer trigger** (`timer_trigger`) | **Yes** | **Yes** | Needs uenum type field + composite schemas for interval/cron. See Timer Trigger section |
100
+ | **Control ops** (`if_else`, `while`, `router`) | **Yes** | **Yes** | Need input schema + named output ports (true/false/error, block_start/output/error) |
101
+ | **Loop ops** (`loop_over_*`) | **Yes** | **Yes** | Need input schema (filter fields) + `block_callback` input port + `block_start`/`output`/`error` output ports. `block_start` uses `type: "block_start"` |
102
+ | **`for_each`** | **Yes** | **Yes** | Dynamic schema — avoid in templates, use `loop_over_*` instead |
103
+ | **`init_variable`** | **Yes** | **Yes** | Output port needs `scope_variables` array. See Variable Management section |
104
+ | **`set_variable`** | **Yes** | **Yes** | Input port needs `uenum` fields with `allowed_values`. See Variable Management section |
105
+ | **Action ops** (`update_*`, `create_*`, `get_*`, etc.) | **Yes** | No | Need input schema so UI renders field picker. Get schemas from `operations/schemas/<slug>.md` raw JSON files at `/tmp/op_schemas/<slug>.json` |
106
+ | **`invoke_code`** | **Yes** | **Yes** | Dynamic schema with `invalidate_on_field_update`. See `operations/schemas/invoke_code.md` for full patterns |
107
+ | **`manual_trigger`** | No | **Yes** | Has dynamic output schema |
108
+
109
+ **Port schema format** — uses the internal schema format with `field_descriptors`, `composite_schemas`, `data_name`, `db_name`, `oasis` metadata. Get the raw JSON from `/tmp/op_schemas/<slug>.json` (downloaded from `devrev/flow` repo `.gen.json` files) and include the relevant port's schema object.
110
+
111
+ **Error ports** — Control ops and `invoke_code` should include an `error` output port:
112
+ ```json
113
+ {"name": "error", "schema": {"field_descriptors": [
114
+ {"field_type": "text", "data_name": "message", "db_name": "message", "description": "Error message with more details about the error", "name": "message", "oasis": {"name": "message"}, "ui": {"display_name": "Error Message"}},
115
+ {"field_type": "enum", "allowed_values": ["bad_request", "not_found", "internal"], "data_name": "type", "db_name": "type", "description": "Type of the error", "name": "type", "oasis": {"name": "type"}, "ui": {"display_name": "Error Type"}}
116
+ ], "type": "field_descriptor"}, "type": "error"}
117
+ ```
118
+
119
+ **`if_else` ports:**
120
+ ```json
121
+ "input_ports": [{"name": "input", "schema": {"field_descriptors": [{"field_type": "struct", "data_name": "condition", "db_name": "condition", "description": "Condition to evaluate", "is_required": true, "name": "condition", "oasis": {"name": "condition"}}], "type": "field_descriptor"}, "type": "default"}],
122
+ "output_ports": [{"name": "true", "type": "default"}, {"name": "false", "type": "default"}, <error_port>]
123
+ ```
124
+
125
+ **`invoke_code` ports** — include `input_ports` with variable composite + code editor schema, `output_ports` with empty output + error. Do NOT include `input_values` — the user configures code, input variables, and output schema in the UI after import. See `operations/schemas/invoke_code.md` for the full `input_ports` JSON.
126
+
127
+ ## How Operations Are Referenced
128
+
129
+ Always use `namespace` + `slug`:
130
+
131
+ ```json
132
+ "operation": {
133
+ "namespace": "devrev",
134
+ "slug": "ticket_created"
135
+ }
136
+ ```
137
+
138
+ - First-party DevRev operations: `"devrev"` namespace
139
+ - Custom objects: `"custom_object"` namespace
140
+ - Third-party snap-in operations: use their snap-in namespace (e.g. `"send-emails"`)
141
+ - Look up the correct slug from `.claude/skills/create-workflow-template/operations/*.md` files
142
+
143
+ ## Connecting Steps (next_steps)
144
+
145
+ Each step can connect to one or more next steps via port-based edges:
146
+
147
+ ```json
148
+ "next_steps": [
149
+ {
150
+ "port_name": "output",
151
+ "next_step_reference_key": "target_step_ref",
152
+ "next_port_name": "input"
153
+ }
154
+ ]
155
+ ```
156
+
157
+ **Port names by operation type:**
158
+
159
+ | Operation | Output Ports |
160
+ |-----------|-------------|
161
+ | Regular triggers/actions | `"output"` |
162
+ | `if_else` | `"true"` and `"false"` |
163
+ | `loop_over_*` / `for_each` / `while` | `"block_start"` (loop body) and `"output"` (after loop) |
164
+ | `router` | Named route ports |
165
+
166
+ The `next_port_name` on the target step is almost always `"input"`.
167
+
168
+ ## Input Values (Data Mapping)
169
+
170
+ The `input_values` array defines how data flows into each step. **Use string type names** (not integer IDs):
171
+
172
+ ```json
173
+ "input_values": [
174
+ {
175
+ "port_name": "input",
176
+ "fields": [
177
+ {
178
+ "name": "field_name",
179
+ "value": {
180
+ "type": "<type_name>",
181
+ "value": <value>
182
+ }
183
+ }
184
+ ]
185
+ }
186
+ ]
187
+ ```
188
+
189
+ ### Value Types
190
+
191
+ | Type Name | Description | Example |
192
+ |-----------|-------------|---------|
193
+ | `"literal"` | Static/fixed value | `{"type": "literal", "value": "internal"}` |
194
+ | `"jsonata_expression"` | References other steps' outputs | `{"type": "jsonata_expression", "value": "$get('step_ref').field"}` |
195
+ | `"composite_value"` | Structured object with nested fields | See composite section below |
196
+ | `"text_template"` | Template string with embedded expressions | `{"type": "text_template", "value": "Hello {% expr $get('step_ref').name %}"}` |
197
+ | `"list_value"` | List of items | See list section below |
198
+
199
+ ### Literal Values
200
+
201
+ Use for static values — strings, numbers, booleans, DON IDs, or structured objects:
202
+
203
+ ```json
204
+ { "type": "literal", "value": "internal" }
205
+ { "type": "literal", "value": true }
206
+ { "type": "literal", "value": "don:core:dvrv-us-1:devo/0:custom_stage/27" }
207
+ { "type": "literal", "value": {"seconds": 30} }
208
+ ```
209
+
210
+ ### JSONata Expressions
211
+
212
+ Reference outputs from previous steps:
213
+
214
+ ```json
215
+ { "type": "jsonata_expression", "value": "$get('issue_created_1', 'output').id" }
216
+ { "type": "jsonata_expression", "value": "$get('get_ticket_1', 'output').owned_by[0].id" }
217
+ { "type": "jsonata_expression", "value": "$get('issue_updated_1', 'output').sprint.end_date" }
218
+ ```
219
+
220
+ Syntax: `$get('<reference_key>', '<port_name>').<field_path>`
221
+ - Port name defaults to `'output'` if omitted: `$get('step_ref').field`
222
+
223
+ ### Text Templates
224
+
225
+ Mix static text with embedded expressions:
226
+
227
+ ```json
228
+ { "type": "text_template", "value": "Hi {% expr $get('get_ticket_1', 'output').owned_by[0].id %}, the ticket has been escalated." }
229
+ ```
230
+
231
+ ### List Values
232
+
233
+ Wrap arrays — each item specifies its own type:
234
+
235
+ ```json
236
+ {
237
+ "type": "list_value",
238
+ "value": {
239
+ "items": [
240
+ { "type": "literal", "value": ["stage"] },
241
+ { "type": "jsonata_expression", "value": "$append($get('step_ref', 'output').owned_by, [])" }
242
+ ]
243
+ }
244
+ }
245
+ ```
246
+
247
+ **Common pattern** — wrapping a single expression as a list (required for array-typed fields like `receivers`, `fields_to_watch`):
248
+ ```json
249
+ {
250
+ "type": "list_value",
251
+ "value": {
252
+ "items": [
253
+ { "type": "jsonata_expression", "value": "$append($get('trigger_1', 'output').owned_by, [])" }
254
+ ]
255
+ }
256
+ }
257
+ ```
258
+
259
+ ### Composite Values
260
+
261
+ Structured objects with named sub-fields:
262
+
263
+ ```json
264
+ {
265
+ "type": "composite_value",
266
+ "value": {
267
+ "fields": [
268
+ { "name": "set", "value": { "type": "literal", "value": ["don:identity:dvrv-us-1:devo/0:devu/1"] } }
269
+ ]
270
+ }
271
+ }
272
+ ```
273
+
274
+ ## Trigger Configuration
275
+
276
+ Trigger steps are regular steps that use a trigger-type operation. They support two special input fields:
277
+
278
+ ### `fields_to_watch` — Filter which field changes fire the trigger
279
+
280
+ ```json
281
+ {
282
+ "name": "fields_to_watch",
283
+ "value": {
284
+ "type": "list_value",
285
+ "value": {
286
+ "items": [
287
+ { "type": "literal", "value": ["stage"] }
288
+ ]
289
+ }
290
+ }
291
+ }
292
+ ```
293
+
294
+ ### `_filter` — Conditional filter for when the trigger fires
295
+
296
+ ```json
297
+ {
298
+ "name": "_filter",
299
+ "value": {
300
+ "type": "literal",
301
+ "value": {
302
+ "conditions": [{
303
+ "conditions": [{
304
+ "operands": [
305
+ { "type": "jsonata_expression", "value": "$get('opportunity_updated_1', 'output').stage.name" },
306
+ { "type": "literal", "value": "3-evaluate" }
307
+ ],
308
+ "operator": "equals",
309
+ "type": "rule"
310
+ }],
311
+ "logical_operator": "and",
312
+ "negate": false,
313
+ "type": "group"
314
+ }],
315
+ "logical_operator": "and",
316
+ "negate": false,
317
+ "type": "group"
318
+ }
319
+ }
320
+ }
321
+ ```
322
+
323
+ ## Timer Trigger
324
+
325
+ The `timer_trigger` operation requires full `input_ports` and `output_ports` (unlike event-based triggers which resolve schemas automatically).
326
+
327
+ **Input port** — includes `uenum` type field and composite schemas for interval/cron:
328
+ ```json
329
+ "input_ports": [{"name": "input", "schema": {
330
+ "composite_schemas": [
331
+ {"description": "Timer interval configuration", "fields": [
332
+ {"field_type": "enum", "allowed_values": ["days", "hours"], "data_name": "unit", "db_name": "unit",
333
+ "default_value": "days", "description": "Unit of time for the interval", "is_required": true,
334
+ "name": "unit", "oasis": {"name": "unit"}, "ui": {"display_name": "Unit of time"}},
335
+ {"field_type": "int", "data_name": "number_of_units", "db_name": "number_of_units",
336
+ "default_value": 1, "description": "Number of Units", "is_required": true,
337
+ "name": "number_of_units", "oasis": {"name": "number_of_units"}, "ui": {"display_name": "Number of Units"}}
338
+ ], "name": "timer_interval"},
339
+ {"description": "Timer cron configuration", "fields": [
340
+ {"field_type": "tokens", "data_name": "schedule", "db_name": "schedule",
341
+ "description": "The cron expression for the timer trigger.", "is_required": true,
342
+ "name": "schedule", "oasis": {"name": "schedule"}}
343
+ ], "name": "timer_cron"}
344
+ ],
345
+ "field_descriptors": [
346
+ {"field_type": "uenum", "allowed_values": [
347
+ {"id": 1, "label": "Interval based", "ordinal": 1, "tooltip": "Trigger at regular intervals", "value": "interval"},
348
+ {"id": 2, "label": "Cron based", "ordinal": 2, "tooltip": "Trigger at specific time using cron", "value": "cron"}
349
+ ], "data_name": "type", "db_name": "type", "default_value": 1,
350
+ "description": "The type of timer trigger", "name": "type", "oasis": {"name": "type"}, "ui": {"display_name": "Type"}},
351
+ {"field_type": "composite", "composite_type": "timer_interval", "data_name": "interval", "db_name": "interval",
352
+ "description": "The interval at which the workflow should trigger.", "is_required": false,
353
+ "name": "interval", "oasis": {"name": "interval"}},
354
+ {"field_type": "composite", "composite_type": "timer_cron", "data_name": "cron", "db_name": "cron",
355
+ "description": "The cron configuration.", "is_required": false, "name": "cron", "oasis": {"name": "cron"}},
356
+ {"field_type": "struct", "data_name": "_filter", "db_name": "_filter", "description": "Filter for the trigger event",
357
+ "name": "_filter", "oasis": {"name": "_filter"}, "ui": {"display_name": "Filter"}}
358
+ ], "type": "field_descriptor"}, "type": "default"}]
359
+ ```
360
+
361
+ **Output port** — exposes the scheduled time:
362
+ ```json
363
+ "output_ports": [{"name": "output", "schema": {"field_descriptors": [
364
+ {"field_type": "timestamp", "data_name": "scheduled_time", "db_name": "scheduled_time",
365
+ "description": "The time when the workflow was scheduled to trigger", "is_required": true,
366
+ "name": "scheduled_time", "oasis": {"name": "scheduled_time"}}
367
+ ], "type": "field_descriptor"}, "type": "default"}]
368
+ ```
369
+
370
+ ## Critical Field Type Requirements
371
+
372
+ ### `uenum` Fields MUST Have `allowed_values`
373
+
374
+ Fields with `field_type: "uenum"` will cause import error `"Missing required field: allowed_values"` if `allowed_values` is missing. Each value must be an object with `{id, label, ordinal, value}`:
375
+
376
+ ```json
377
+ {"field_type": "uenum", "allowed_values": [
378
+ {"id": 1, "label": "Set Value", "ordinal": 1, "value": "set"},
379
+ {"id": 2, "label": "Concatenate", "ordinal": 2, "value": "concat"}
380
+ ], "data_name": "operation", ...}
381
+ ```
382
+
383
+ The `value` can be a string or a complex object (e.g., for variable references):
384
+ ```json
385
+ {"id": 1, "label": "init_variable_1 - summary", "ordinal": 1,
386
+ "value": {"output_port_name": "output", "step_reference_key": "init_variable_1", "variable_name": "summary"}}
387
+ ```
388
+
389
+ ### `array` Fields MUST Have `base_type`
390
+
391
+ Fields with `field_type: "array"` will cause import error `"discriminator not set: base_type"` if `base_type` is missing:
392
+
393
+ ```json
394
+ {"field_type": "array", "base_type": "id", "data_name": "owned_by", ...}
395
+ {"field_type": "array", "base_type": "text", "data_name": "external_ref", ...}
396
+ {"field_type": "array", "base_type": "composite", "composite_type": "pair", "data_name": "headers", ...}
397
+ {"field_type": "array", "base_type": "enum", "allowed_values": ["days", "hours"], "data_name": "units", ...}
398
+ ```
399
+
400
+ Common `base_type` values: `id`, `text`, `composite`, `enum`
401
+
402
+ ## If/Else Conditions
403
+
404
+ The `if_else` operation takes a structured condition as a literal:
405
+
406
+ ```json
407
+ {
408
+ "name": "condition",
409
+ "value": {
410
+ "type": "literal",
411
+ "value": {
412
+ "type": "group",
413
+ "logical_operator": "and",
414
+ "negate": false,
415
+ "conditions": [{
416
+ "conditions": [{
417
+ "operands": [
418
+ { "type": "jsonata_expression", "value": "$get('get_account_1', 'output').tnt__segment" }
419
+ ],
420
+ "operator": "exists",
421
+ "type": "rule"
422
+ }],
423
+ "logical_operator": "and",
424
+ "negate": false,
425
+ "type": "group"
426
+ }]
427
+ }
428
+ }
429
+ }
430
+ ```
431
+
432
+ **Condition operators:** `equals`, `not_equals`, `contains_any`, `is_empty`, `is_not_empty`, `greater_than`, `less_than`, `greater_than_or_equal`, `less_than_or_equal`, `contains`, `not_contains`, `starts_with`, `ends_with`, `exists`, `not_exists`
433
+
434
+ **Logical operators:** `and`, `or`
435
+
436
+ **Note:** Conditions are typically nested as groups-within-groups (see the `_filter` example above). Each rule has `operands` (each with `type` and `value`), `operator`, and `type: "rule"`. Rules are wrapped in `type: "group"` with `logical_operator`.
437
+
438
+ ## Loops and Iteration
439
+
440
+ ### `loop_over_*` Operations (Preferred for Native Objects)
441
+
442
+ For iterating over DevRev native objects, use the dedicated `loop_over_*` operations. These have **static, well-defined schemas** that import reliably. Available operations:
443
+
444
+ | Operation | Object Type |
445
+ |-----------|------------|
446
+ | `loop_over_issues` | Issues |
447
+ | `loop_over_tickets` | Tickets |
448
+ | `loop_over_accounts` | Accounts |
449
+ | `loop_over_articles` | Articles |
450
+ | `loop_over_customers` | Customers |
451
+ | `loop_over_dev_users` | Dev Users |
452
+ | `loop_over_enhancements` | Enhancements |
453
+ | `loop_over_incidents` | Incidents |
454
+ | `loop_over_meetings` | Meetings |
455
+ | `loop_over_opportunity` | Opportunities |
456
+ | `loop_over_sprints` | Sprints |
457
+
458
+ **Key structural requirements for `loop_over_*`:**
459
+
460
+ 1. **`block_callback` input port** — MUST be included:
461
+ ```json
462
+ {"name": "block_callback", "schema": {"type": "field_descriptor"}, "type": "block_callback"}
463
+ ```
464
+
465
+ 2. **`block_start` output port** — uses `type: "block_start"` (NOT `"default"`):
466
+ ```json
467
+ {"name": "block_start", "schema": {"type": "field_descriptor"}, "type": "block_start"}
468
+ ```
469
+
470
+ 3. **Two `next_steps` edges** — `block_start` to loop body, `output` to after-loop step:
471
+ ```json
472
+ "next_steps": [
473
+ {"port_name": "block_start", "next_step_reference_key": "first_body_step", "next_port_name": "input"},
474
+ {"port_name": "output", "next_step_reference_key": "after_loop_step", "next_port_name": "input"}
475
+ ]
476
+ ```
477
+
478
+ 4. **Block body steps** — MUST set `block_step_reference_key` pointing to the loop:
479
+ ```json
480
+ "block_step_reference_key": "loop_over_issues_1"
481
+ ```
482
+
483
+ 5. **Accessing current item** — Fields are directly on `block_start` (NO `.item` prefix):
484
+ ```json
485
+ "$get('loop_over_issues_1', 'block_start').id"
486
+ "$get('loop_over_issues_1', 'block_start').title"
487
+ "$get('loop_over_issues_1', 'block_start').stage.name"
488
+ ```
489
+
490
+ 6. **Input port schema** — Copy the full schema from the operation's schema file (`operations/schemas/loop_over_issues.md`). The `input_ports` include `input` (with filter fields like `owned_by`, `state`, `stages`, `limit`) and `block_callback`.
491
+
492
+ ### `for_each` (Only When No Native Loop Exists)
493
+
494
+ **WARNING:** `for_each` has a **dynamic schema** (populated by a schema handler at runtime). Its `items_to_iterate` field has `field_type: "array"` but requires a `base_type` that is resolved dynamically — this makes templates using `for_each` prone to import errors like `"discriminator not set: base_type"`. **Only use `for_each` when no dedicated `loop_over_*` operation exists for the object type you need to iterate.**
495
+
496
+ The `for_each` operation still uses the same block body patterns:
497
+ - `block_step_reference_key` on body steps
498
+ - `block_start` / `output` port names for next_steps
499
+ - `block_callback` input port
500
+ - Access current item: `$get('for_each_1', 'block_start').item`
501
+
502
+ ### Variable Management (init_variable + set_variable + $get_variable)
503
+
504
+ Variables allow accumulating data across loop iterations (e.g., building a summary string). The complete pattern is:
505
+
506
+ #### Step 1: `init_variable` — Create the variable before the loop
507
+
508
+ **Input port** with `variables` field (composite `devrev:schema`):
509
+ ```json
510
+ "input_ports": [{"name": "input", "schema": {"field_descriptors": [
511
+ {"field_type": "composite", "composite_type": "devrev:schema", "data_name": "variables", "db_name": "variables",
512
+ "description": "The variables to initialize.", "name": "variables", "oasis": {"name": "variables"}}
513
+ ], "invalidate_on_field_update": ["variables"], "type": "field_descriptor"}, "type": "default"}]
514
+ ```
515
+
516
+ **Input values** — define the variable schema:
517
+ ```json
518
+ "input_values": [{"port_name": "input", "fields": [{"name": "variables", "value": {"type": "literal", "value": {
519
+ "fields": [{"description": "", "field_type": "text", "is_filterable": false, "name": "summary",
520
+ "ui": {"create_view": {}, "detail_view": {"is_hidden": true}, "display_name": "summary", "summary_view": {"is_hidden": true}}
521
+ }]
522
+ }}}]}]
523
+ ```
524
+
525
+ **Output port** — MUST include `scope_variables` array matching the variable definition:
526
+ ```json
527
+ "output_ports": [{"name": "output", "schema": {"type": "field_descriptor"},
528
+ "scope_variables": [{"field_descriptor": {
529
+ "field_type": "text", "data_name": "summary", "db_name": "summary", "description": "",
530
+ "is_filterable": false, "name": "summary", "oasis": {"name": "summary"},
531
+ "ui": {"create_view": {}, "detail_view": {"is_hidden": true}, "display_name": "summary", "summary_view": {"is_hidden": true}}
532
+ }}],
533
+ "type": "default"}, <error_port>]
534
+ ```
535
+
536
+ #### Step 2: `set_variable` — Modify the variable inside the loop body
537
+
538
+ The `set_variable` step requires `uenum` fields with `allowed_values`. See the **uenum field type** section below.
539
+
540
+ **Input port** with three fields — `variable` (uenum), `operation` (uenum), `value` (text):
541
+ ```json
542
+ "input_ports": [{"name": "input", "schema": {"field_descriptors": [
543
+ {"field_type": "uenum", "allowed_values": [
544
+ {"id": 1, "label": "init_variable_1 - summary", "ordinal": 1,
545
+ "value": {"output_port_name": "output", "step_reference_key": "init_variable_1", "variable_name": "summary"}}
546
+ ], "data_name": "variable", "db_name": "variable", "description": "Variable",
547
+ "is_required": true, "name": "variable", "oasis": {"name": "variable"}, "ui": {"display_name": "Variable"}},
548
+ {"field_type": "uenum", "allowed_values": [
549
+ {"id": 1, "label": "Set Value", "ordinal": 1, "value": "set"},
550
+ {"id": 2, "label": "Concatenate", "ordinal": 2, "value": "concat"}
551
+ ], "data_name": "operation", "db_name": "operation", "description": "Operation to perform.",
552
+ "is_required": false, "name": "operation", "oasis": {"name": "operation"}, "ui": {"display_name": "Operation"}},
553
+ {"field_type": "text", "data_name": "value", "db_name": "value", "description": "Value",
554
+ "is_filterable": false, "is_required": true, "name": "value", "oasis": {"name": "value"}, "ui": {"display_name": "Value"}}
555
+ ], "invalidate_on_field_update": ["variable"], "type": "field_descriptor"}, "type": "default"}]
556
+ ```
557
+
558
+ **Input values** — specify which variable, what operation, and what value:
559
+ ```json
560
+ "input_values": [{"port_name": "input", "fields": [
561
+ {"name": "variable", "value": {"type": "literal", "value": {
562
+ "output_port_name": "output", "step_reference_key": "init_variable_1", "variable_name": "summary"}}},
563
+ {"name": "operation", "value": {"type": "literal", "value": "concat"}},
564
+ {"name": "value", "value": {"type": "text_template", "value": "{% expr $get('http_1', 'output').body %}"}}
565
+ ]}]
566
+ ```
567
+
568
+ #### Step 3: `$get_variable` — Read the accumulated variable after the loop
569
+
570
+ After the loop completes, access the variable using `$get_variable()`:
571
+ ```
572
+ {% expr $get_variable('init_variable_1','summary',{'output_port_name' : 'output'}) %}
573
+ ```
574
+
575
+ Syntax: `$get_variable('<init_step_ref>', '<variable_name>', {'output_port_name': 'output'})`
576
+
577
+ ## Sleep/Wait Operations
578
+
579
+ **Sleep For** (pause for duration — value is a literal object with `seconds`):
580
+ ```json
581
+ {
582
+ "name": "Sleep For",
583
+ "operation": { "namespace": "devrev", "slug": "sleep_for" },
584
+ "reference_key": "sleep_for_1",
585
+ "input_values": [{
586
+ "port_name": "input",
587
+ "fields": [{
588
+ "name": "duration",
589
+ "value": { "type": "literal", "value": {"seconds": 30} }
590
+ }]
591
+ }]
592
+ }
593
+ ```
594
+
595
+ **Sleep Until** (pause until timestamp from another step):
596
+ ```json
597
+ {
598
+ "name": "Sleep Until",
599
+ "operation": { "namespace": "devrev", "slug": "sleep_until" },
600
+ "reference_key": "sleep_until_1",
601
+ "input_values": [{
602
+ "port_name": "input",
603
+ "fields": [{
604
+ "name": "sleep_until",
605
+ "value": { "type": "jsonata_expression", "value": "$get('ticket_created_1', 'output').target_close_date" }
606
+ }]
607
+ }]
608
+ }
609
+ ```
610
+
611
+ ## Ask AI Operation
612
+
613
+ Use `ask_ai` for AI-powered text analysis, classification, or generation:
614
+
615
+ ```json
616
+ {
617
+ "name": "Ask AI",
618
+ "operation": { "namespace": "devrev", "slug": "ask_ai" },
619
+ "reference_key": "ask_ai_1",
620
+ "input_values": [{
621
+ "port_name": "input",
622
+ "fields": [
623
+ { "name": "model", "value": { "type": "literal", "value": "Normal (default)" } },
624
+ { "name": "output_format", "value": { "type": "literal", "value": "text" } },
625
+ {
626
+ "name": "ai_input",
627
+ "value": {
628
+ "type": "text_template",
629
+ "value": "Analyze the following comment and classify as Escalate or Ignore: {% expr $get('timeline_comment_created_1', 'output').body %}"
630
+ }
631
+ }
632
+ ]
633
+ }]
634
+ }
635
+ ```
636
+
637
+ Access the AI response: `$get('ask_ai_1', 'output').ai_output`
638
+
639
+ ## HTTP Operation
640
+
641
+ Use `http` for external API calls:
642
+
643
+ ```json
644
+ {
645
+ "name": "HTTP Request",
646
+ "operation": { "namespace": "devrev", "slug": "http" },
647
+ "reference_key": "http_1",
648
+ "input_values": [{
649
+ "port_name": "input",
650
+ "fields": [
651
+ { "name": "method", "value": { "type": "literal", "value": "POST" } },
652
+ { "name": "auth_type", "value": { "type": "literal", "value": "Bearer" } },
653
+ { "name": "url", "value": { "type": "text_template", "value": "https://api.devrev.ai/rev-users.get" } },
654
+ { "name": "body", "value": { "type": "text_template", "value": "{\"id\":\"{% expr $get('prev_step', 'output').user_id %}\"}" } }
655
+ ]
656
+ }]
657
+ }
658
+ ```
659
+
660
+ ## Code Node (`invoke_code`)
661
+
662
+ The Code node executes custom Python code within a workflow. Use it when native nodes don't provide the flexibility needed — data transformation, complex calculations, text processing, custom business logic, or dynamic value generation.
663
+
664
+ ### How It Works
665
+
666
+ 1. The workflow engine collects `input_values` mapped from previous steps
667
+ 2. Python code executes in a secure sandbox (the `run` function receives inputs as a dict)
668
+ 3. The `run` function returns a dict — returned values become available to downstream nodes via `output_schema`
669
+
670
+ ### Template Structure
671
+
672
+ The `invoke_code` step requires three input fields. **Critical type requirements:**
673
+ - `code` uses `text_template` type (NOT `literal`)
674
+ - `input_values` items use `composite_value_list` (NOT `composite_value`)
675
+ - `output_schema` uses `literal` with full UI metadata on each field
676
+
677
+ ```json
678
+ {
679
+ "name": "Execute Code",
680
+ "description": "Runs custom Python code",
681
+ "reference_key": "invoke_code_1",
682
+ "operation": { "namespace": "devrev", "slug": "invoke_code" },
683
+ "input_values": [{
684
+ "port_name": "input",
685
+ "fields": [
686
+ {
687
+ "name": "code",
688
+ "value": {
689
+ "type": "text_template",
690
+ "value": "import re\n\ndef run(inputs):\n text = inputs.get('description', '')\n cleaned = re.sub(r'\\\\bagent\\\\b', 'computer', text, flags=re.IGNORECASE)\n return {'cleaned': cleaned}"
691
+ }
692
+ },
693
+ {
694
+ "name": "input_values",
695
+ "value": {
696
+ "type": "list_value",
697
+ "value": {
698
+ "items": [{
699
+ "type": "composite_value_list",
700
+ "value": [{
701
+ "fields": [
702
+ { "name": "value", "value": { "type": "jsonata_expression", "value": "$get('trigger_1', 'output').description" } },
703
+ { "name": "name", "value": { "type": "literal", "value": "description" } }
704
+ ]
705
+ }]
706
+ }]
707
+ }
708
+ }
709
+ },
710
+ {
711
+ "name": "output_schema",
712
+ "value": {
713
+ "type": "literal",
714
+ "value": {
715
+ "fields": [{
716
+ "description": "",
717
+ "field_type": "text",
718
+ "is_filterable": false,
719
+ "name": "cleaned",
720
+ "ui": {
721
+ "create_view": {},
722
+ "detail_view": {"is_hidden": true},
723
+ "display_name": "cleaned",
724
+ "summary_view": {"is_hidden": true}
725
+ }
726
+ }]
727
+ }
728
+ }
729
+ }
730
+ ]
731
+ }],
732
+ "next_steps": [
733
+ { "port_name": "output", "next_step_reference_key": "next_step_1", "next_port_name": "input" }
734
+ ]
735
+ }
736
+ ```
737
+
738
+ ### Input Fields
739
+
740
+ | Field | Value Type | Required | Description |
741
+ |-------|------|----------|-------------|
742
+ | `code` | `text_template` | Yes | Python code with a `def run(inputs):` function that returns a dict |
743
+ | `input_values` | `list_value` of `composite_value_list` | No | Array of `{name, value}` pairs passed as the `inputs` dict |
744
+ | `output_schema` | `literal` object with UI metadata | Yes* | `{fields: [{name, field_type, description, is_filterable, ui}]}` — defines outputs available downstream |
745
+
746
+ *Output schema is required if downstream steps reference the code's outputs.
747
+
748
+ ### Output Port Schema
749
+
750
+ The `output` port's `field_descriptors` must match the `output_schema` fields:
751
+ ```json
752
+ "output_ports": [
753
+ {"name": "output", "schema": {"field_descriptors": [
754
+ {"field_type": "text", "data_name": "cleaned", "db_name": "cleaned", "description": "cleaned", "name": "cleaned", "oasis": {"name": "cleaned"}}
755
+ ], "type": "field_descriptor"}, "type": "default"},
756
+ <error_port>
757
+ ]
758
+ ```
759
+
760
+ ### Output Schema Field Types
761
+
762
+ | `field_type` | Python Return Type | Example |
763
+ |-------------|-------------------|---------|
764
+ | `text` | `str` | `"hello"` |
765
+ | `int` | `int` | `42` |
766
+ | `double` | `float` | `3.14` |
767
+ | `bool` | `bool` | `True` / `False` |
768
+ | `timestamp` | `str` (ISO 8601) | `"2026-01-28T18:08:38+0000"` |
769
+ | `id` | `str` (DevRev ID) | `"don:core:dvrv-us-1:devo/0:ticket/123"` |
770
+ | `[]text` | `list[str]` | `["a", "b", "c"]` |
771
+
772
+ ### Accessing Code Outputs
773
+
774
+ Downstream steps reference code outputs via JSONata:
775
+
776
+ ```
777
+ $get('invoke_code_1', 'output').word_count
778
+ $get('invoke_code_1', 'output').is_long
779
+ ```
780
+
781
+ ### Python Environment
782
+
783
+ **Execution limits:**
784
+ - Timeout: 30s default, max 120s (configurable)
785
+ - Memory: 256MB
786
+ - Output + log size: 512KB
787
+
788
+ **Available libraries:** `json`, `re`, `math`, `datetime`, `requests`, `collections`, `itertools`, `functools`, `string`, `random`, `decimal`, `statistics`, `typing`, `copy`, `textwrap`, `hashlib`, `hmac`, `secrets`, `base64`, `uuid`, `urllib`, `html`, `xml`, `csv`, `zipfile`, `gzip`, `zoneinfo`, `io`, `dataclasses`, `operator`
789
+
790
+ **Blocked libraries:** `os`, `sys`, `subprocess`, `shutil`, `pathlib`, `multiprocessing`, `threading`, `concurrent`, `pickle`
791
+
792
+ ### Code Patterns
793
+
794
+ **Data transformation:**
795
+ ```python
796
+ def run(inputs):
797
+ tags_str = inputs.get('tags', '')
798
+ return {'tag_list': [t.strip() for t in tags_str.split(',') if t.strip()]}
799
+ ```
800
+
801
+ **Text processing with regex:**
802
+ ```python
803
+ import re
804
+ def run(inputs):
805
+ text = inputs.get('text', '')
806
+ emails = re.findall(r'[\\w.-]+@[\\w.-]+\\.\\w+', text)
807
+ cleaned = re.sub(r'@\\w+', '', text).strip()
808
+ return {'emails': emails, 'cleaned_text': cleaned, 'has_emails': len(emails) > 0}
809
+ ```
810
+
811
+ **Conditional routing logic:**
812
+ ```python
813
+ def run(inputs):
814
+ severity = inputs.get('severity', 'low').lower()
815
+ scores = {'critical': 100, 'high': 75, 'medium': 50, 'low': 25}
816
+ score = scores.get(severity, 25)
817
+ return {'priority_score': score, 'escalate': score >= 75, 'team': 'tier-2' if score >= 75 else 'tier-1'}
818
+ ```
819
+
820
+ **Date arithmetic:**
821
+ ```python
822
+ from datetime import datetime, timedelta
823
+ def run(inputs):
824
+ created = inputs.get('created_date', '')
825
+ dt = datetime.fromisoformat(created.replace('Z', '+00:00'))
826
+ sla_deadline = (dt + timedelta(hours=24)).isoformat()
827
+ return {'sla_deadline': sla_deadline}
828
+ ```
829
+
830
+ **Dynamic message generation:**
831
+ ```python
832
+ def run(inputs):
833
+ name = inputs.get('customer_name', 'Customer')
834
+ ticket_id = inputs.get('ticket_id', 'N/A')
835
+ summary = inputs.get('summary', '')[:100]
836
+ msg = f"Hello {name}, your ticket #{ticket_id} has been received. Summary: {summary}"
837
+ return {'notification_message': msg, 'subject_line': f'[#{ticket_id}] Support request received'}
838
+ ```
839
+
840
+ **HTTP request (using requests library):**
841
+ ```python
842
+ import requests
843
+ def run(inputs):
844
+ url = inputs.get('api_url', '')
845
+ resp = requests.get(url, timeout=10)
846
+ data = resp.json()
847
+ return {'status': resp.status_code, 'result': data.get('result', '')}
848
+ ```
849
+
850
+ ### Best Practices
851
+
852
+ - Always use `inputs.get('key', default)` — never `inputs['key']`
853
+ - Define output schema for every key returned — outputs are invisible downstream without it
854
+ - Add `try`/`except` for error handling; return error info in outputs
855
+ - Keep code focused — one Code node per logical operation
856
+ - Use `\\n` for newlines when embedding code in the JSON `code` field (the value is a single string)
857
+
858
+ ## UI Metadata
859
+
860
+ Assign positions to steps for visual layout. Space them logically:
861
+ - Triggers at the top (y ~0)
862
+ - Subsequent steps flow downward (increment y by ~150)
863
+ - Branches spread horizontally (vary x by ~200-300)
864
+
865
+ ```json
866
+ "ui_metadata": {
867
+ "position": { "x": 0, "y": 0 }
868
+ }
869
+ ```
870
+
871
+ ## Reference Key Naming Convention
872
+
873
+ Use the operation slug with a numeric suffix (matching the pattern from example templates):
874
+ - Triggers: `ticket_created_1`, `issue_updated_1`, `opportunity_updated_1`
875
+ - Actions: `add_comment_1`, `update_ticket_1`, `send_notification_1`, `get_account_1`
876
+ - Controls: `if_else_1`, `for_each_1`, `while_1`
877
+ - Blockings: `sleep_for_1`, `sleep_until_1`
878
+
879
+ ## Available Operations
880
+
881
+ Consult these files for the full list of available operations:
882
+
883
+ - **Triggers (145):** `.claude/skills/create-workflow-template/operations/triggers.md`
884
+ - **Actions (245):** `.claude/skills/create-workflow-template/operations/actions.md`
885
+ - **Blockings (2):** `.claude/skills/create-workflow-template/operations/blockings.md`
886
+ - **Controls (8):** `.claude/skills/create-workflow-template/operations/controls.md`
887
+
888
+ ## Operation Schemas (Input/Output Ports)
889
+
890
+ For 106 operations, detailed resolved schemas are available with exact field names, types, required flags, enum values, and composite sub-fields:
891
+
892
+ - **Schema Index:** `.claude/skills/create-workflow-template/operations/schema-index.md` (39 triggers, 62 actions, 5 other)
893
+ - **Per-operation schemas:** `.claude/skills/create-workflow-template/operations/schemas/<slug>.md`
894
+
895
+ Use these schemas to determine the exact field names and types when building `input_values` for a step. For example, to see what fields `create_ticket` accepts, read `operations/schemas/create_ticket.md`.
896
+
897
+ ## Example Templates
898
+
899
+ Study these real-world templates in `.claude/skills/create-workflow-template/examples/` for patterns:
900
+
901
+ ### Validated Working Examples (`working-*.json`)
902
+
903
+ These templates have been confirmed to import successfully. **Always study these first** when building similar workflows:
904
+
905
+ | File | Steps | Pattern Demonstrated |
906
+ |------|-------|---------------------|
907
+ | `working-loop-variable-sample.json` | 7 | **Loop + variable pattern**: timer_trigger -> init_variable -> loop_over_issues -> [http -> set_variable] -> ask_ai -> send_notification. Shows `loop_over_*`, `init_variable`, `set_variable`, `$get_variable`, `block_step_reference_key`, `uenum` fields, `block_callback` port, timer trigger with uenum |
908
+ | `working-invoke-code-sample.json` | 4 | **Code node pattern**: enhancement_updated -> if_else -> invoke_code -> update_enhancement. Shows `invoke_code` with `text_template` code, `composite_value_list` inputs, `output_schema` with UI metadata |
909
+ | `working-csat-score-on-ticket-resolved.json` | 5 | **HTTP + AI pattern**: ticket_updated -> if_else (check resolved) -> http (timeline-entries API) -> ask_ai (CSAT analysis) -> add_comment. Shows HTTP with Bearer auth, `text_template` body with expressions, AI prompt composition |
910
+ | `working-enhancement-replace-agent.json` | 4 | **Code + update pattern**: enhancement_updated -> if_else (contains check) -> invoke_code (regex replace) -> update_enhancement. Shows `invoke_code` with Python regex, passing data between steps |
911
+
912
+ ### Reference Examples (from production)
913
+
914
+ | File | Steps | Pattern Demonstrated |
915
+ |------|-------|---------------------|
916
+ | `4392-Async opportunity review agent` | 2 | Simple trigger -> action, agent interaction |
917
+ | `4505-Auto-update issue tcd` | 3 | Trigger with `fields_to_watch` + `_filter`, chained updates |
918
+ | `5216-Account segment missing notification` | 4 | Trigger -> get -> if_else -> comment, `_filter` with stage check |
919
+ | `4441-Ticket escalator from customer message` | 11 | Complex: trigger -> ask_ai -> if_else -> get -> nested if_else -> update + notify |
920
+ | `3592-Generate rca from pia` | 8 | Incident trigger -> ask_ai -> create_article, if_else branching |
921
+ | `5040-Devrevu enablement journey poc emails` | 8 | Custom object trigger, HTTP calls, sleep_for, third-party operations |
922
+ | `5158-Devrevu enablement journey mailing` | 43 | Complex sequential flow with many sleep_for + if_else + third-party email |
923
+
924
+ To read an example: `python3 -c "import json; d=json.load(open('.claude/skills/create-workflow-template/examples/FILENAME.json')); inner=json.loads(d['data']); print(json.dumps(inner, indent=2))"`
925
+
926
+ ## Complete Example: Opportunity Updated -> Check Account -> Notify
927
+
928
+ Based on the real `5216-Account segment missing notification` template pattern:
929
+
930
+ ```json
931
+ {
932
+ "serialization_version": { "major": 2, "minor": 0, "patch": 0 },
933
+ "title": "Account Segment Missing Notification",
934
+ "description": "When an opportunity moves to evaluate stage, check if the account has a segment set. If not, notify the opportunity owner.",
935
+ "steps": [
936
+ {
937
+ "name": "Opportunity Updated",
938
+ "description": "Triggers when an opportunity is updated",
939
+ "reference_key": "opportunity_updated_1",
940
+ "operation": { "namespace": "devrev", "slug": "opportunity_updated" },
941
+ "input_values": [{
942
+ "port_name": "input",
943
+ "fields": [
944
+ {
945
+ "name": "fields_to_watch",
946
+ "value": {
947
+ "type": "list_value",
948
+ "value": { "items": [{ "type": "literal", "value": ["stage"] }] }
949
+ }
950
+ },
951
+ {
952
+ "name": "_filter",
953
+ "value": {
954
+ "type": "literal",
955
+ "value": {
956
+ "type": "group",
957
+ "logical_operator": "and",
958
+ "negate": false,
959
+ "conditions": [{
960
+ "type": "group",
961
+ "logical_operator": "and",
962
+ "negate": false,
963
+ "conditions": [{
964
+ "type": "rule",
965
+ "operator": "equals",
966
+ "operands": [
967
+ { "type": "jsonata_expression", "value": "$get('opportunity_updated_1', 'output').stage.name" },
968
+ { "type": "literal", "value": "3-evaluate" }
969
+ ]
970
+ }]
971
+ }]
972
+ }
973
+ }
974
+ }
975
+ ]
976
+ }],
977
+ "next_steps": [
978
+ { "port_name": "output", "next_step_reference_key": "get_account_1", "next_port_name": "input" }
979
+ ],
980
+ "ui_metadata": { "position": { "x": 0, "y": 0 } }
981
+ },
982
+ {
983
+ "name": "Get Account",
984
+ "description": "Fetch the account linked to the opportunity",
985
+ "reference_key": "get_account_1",
986
+ "operation": { "namespace": "devrev", "slug": "get_account" },
987
+ "input_values": [{
988
+ "port_name": "input",
989
+ "fields": [{
990
+ "name": "id",
991
+ "value": { "type": "jsonata_expression", "value": "$get('opportunity_updated_1', 'output').account.id" }
992
+ }]
993
+ }],
994
+ "next_steps": [
995
+ { "port_name": "output", "next_step_reference_key": "if_else_1", "next_port_name": "input" }
996
+ ],
997
+ "ui_metadata": { "position": { "x": 0, "y": 150 } }
998
+ },
999
+ {
1000
+ "name": "Check Segment Exists",
1001
+ "description": "Check if the account has a segment set",
1002
+ "reference_key": "if_else_1",
1003
+ "operation": { "namespace": "devrev", "slug": "if_else" },
1004
+ "input_values": [{
1005
+ "port_name": "input",
1006
+ "fields": [{
1007
+ "name": "condition",
1008
+ "value": {
1009
+ "type": "literal",
1010
+ "value": {
1011
+ "type": "group",
1012
+ "logical_operator": "and",
1013
+ "negate": false,
1014
+ "conditions": [{
1015
+ "type": "group",
1016
+ "logical_operator": "and",
1017
+ "negate": false,
1018
+ "conditions": [{
1019
+ "type": "rule",
1020
+ "operator": "exists",
1021
+ "operands": [
1022
+ { "type": "jsonata_expression", "value": "$get('get_account_1', 'output').tnt__segment" }
1023
+ ]
1024
+ }]
1025
+ }]
1026
+ }
1027
+ }
1028
+ }]
1029
+ }],
1030
+ "next_steps": [
1031
+ { "port_name": "true", "next_step_reference_key": "add_comment_1", "next_port_name": "input" }
1032
+ ],
1033
+ "ui_metadata": { "position": { "x": 0, "y": 300 } }
1034
+ },
1035
+ {
1036
+ "name": "Add Comment",
1037
+ "description": "Notify the opportunity owner about missing segment",
1038
+ "reference_key": "add_comment_1",
1039
+ "operation": { "namespace": "devrev", "slug": "add_comment" },
1040
+ "input_values": [{
1041
+ "port_name": "input",
1042
+ "fields": [
1043
+ {
1044
+ "name": "object",
1045
+ "value": { "type": "jsonata_expression", "value": "$get('opportunity_updated_1', 'output').id" }
1046
+ },
1047
+ {
1048
+ "name": "visibility",
1049
+ "value": { "type": "literal", "value": "internal" }
1050
+ },
1051
+ {
1052
+ "name": "body",
1053
+ "value": {
1054
+ "type": "text_template",
1055
+ "value": "Hey {% expr $get('opportunity_updated_1', 'output').owned_by[0].id %}, the opportunity has moved to evaluate stage but the account is missing a segment. Please update it."
1056
+ }
1057
+ }
1058
+ ]
1059
+ }],
1060
+ "ui_metadata": { "position": { "x": 0, "y": 450 } }
1061
+ }
1062
+ ]
1063
+ }
1064
+ ```
1065
+
1066
+ ## Validation Checklist
1067
+
1068
+ Before outputting the template, verify:
1069
+
1070
+ 1. `serialization_version` is `{"major": 2, "minor": 0, "patch": 0}`
1071
+ 2. `title` is present and 1-256 chars
1072
+ 3. Every step has a unique `reference_key` matching `^[a-zA-Z_][a-zA-Z0-9_]*$`
1073
+ 4. Every step has a valid `operation` with `namespace` and `slug`
1074
+ 5. At least one step is a trigger operation
1075
+ 6. All `next_step_reference_key` values reference existing steps
1076
+ 7. `if_else` steps use `"true"` and `"false"` port names
1077
+ 8. `loop_over_*`/`for_each`/`while` steps use `"block_start"` and `"output"` port names
1078
+ 9. Steps inside loops have `block_step_reference_key` pointing to their parent loop
1079
+ 10. JSONata expressions use `$get('reference_key', 'output')` syntax referencing existing steps
1080
+ 11. Input field names match the operation's input schema (check `.claude/skills/create-workflow-template/operations/*.md`)
1081
+ 12. No circular references in `next_steps`
1082
+ 13. Value types use string names: `"literal"`, `"jsonata_expression"`, `"text_template"`, `"list_value"`, `"composite_value"`, `"composite_value_list"`
1083
+ 14. Trigger `_filter` conditions use the nested group-of-groups structure
1084
+ 15. The final output is wrapped: `{"templateVersion": "2.0.0", "data": "<stringified inner JSON>"}`
1085
+ 16. **All `uenum` fields have `allowed_values`** with `{id, label, ordinal, value}` objects — missing this causes "Missing required field: allowed_values"
1086
+ 17. **All `array` fields have `base_type`** (`id`, `text`, `composite`, `enum`) — missing this causes "discriminator not set: base_type"
1087
+ 18. **`loop_over_*` steps** have `block_callback` input port and `block_start` output port with `type: "block_start"`
1088
+ 19. **`init_variable` output port** includes `scope_variables` array matching the variable definitions
1089
+ 20. **`invoke_code` code field** uses `text_template` type (not `literal`), input_values items use `composite_value_list` (not `composite_value`)
1090
+ 21. **Use `loop_over_*`** instead of `for_each` for native DevRev objects (issues, tickets, accounts, etc.) — `for_each` has dynamic schemas that cause import errors
1091
+ 22. **After loops**, read accumulated variables with `$get_variable('init_variable_1','var_name',{'output_port_name':'output'})` (not `$get`)