@temporal-architect/claude-plugin 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +38 -0
- package/package.json +37 -0
- package/skills/MANIFEST.json +373 -0
- package/skills/MANIFEST.md +121 -0
- package/skills/temporal-architect/SKILL.md +99 -0
- package/skills/temporal-architect/reference/decomposition.md +78 -0
- package/skills/temporal-architect-author-go/README.md +16 -0
- package/skills/temporal-architect-author-go/SKILL.md +191 -0
- package/skills/temporal-architect-author-go/SUBAGENT_ADOPTION.md +161 -0
- package/skills/temporal-architect-author-go/reference/activity-call.md +73 -0
- package/skills/temporal-architect-author-go/reference/activity-def.md +54 -0
- package/skills/temporal-architect-author-go/reference/assignment.md +36 -0
- package/skills/temporal-architect-author-go/reference/await-all.md +104 -0
- package/skills/temporal-architect-author-go/reference/await-one.md +193 -0
- package/skills/temporal-architect-author-go/reference/await-timer.md +35 -0
- package/skills/temporal-architect-author-go/reference/close.md +71 -0
- package/skills/temporal-architect-author-go/reference/composite-patterns.md +176 -0
- package/skills/temporal-architect-author-go/reference/condition.md +56 -0
- package/skills/temporal-architect-author-go/reference/control-flow.md +151 -0
- package/skills/temporal-architect-author-go/reference/dependency-resolution.md +29 -0
- package/skills/temporal-architect-author-go/reference/detach.md +52 -0
- package/skills/temporal-architect-author-go/reference/heartbeat.md +84 -0
- package/skills/temporal-architect-author-go/reference/nexus-service-def.md +73 -0
- package/skills/temporal-architect-author-go/reference/nexus.md +35 -0
- package/skills/temporal-architect-author-go/reference/options.md +138 -0
- package/skills/temporal-architect-author-go/reference/promise.md +73 -0
- package/skills/temporal-architect-author-go/reference/proto-driven.md +197 -0
- package/skills/temporal-architect-author-go/reference/query-handler.md +34 -0
- package/skills/temporal-architect-author-go/reference/signal-handler.md +73 -0
- package/skills/temporal-architect-author-go/reference/three-layer-testing.md +173 -0
- package/skills/temporal-architect-author-go/reference/types.md +72 -0
- package/skills/temporal-architect-author-go/reference/update-handler.md +64 -0
- package/skills/temporal-architect-author-go/reference/worker.md +215 -0
- package/skills/temporal-architect-author-go/reference/workflow-call.md +37 -0
- package/skills/temporal-architect-author-go/reference/workflow-def.md +45 -0
- package/skills/temporal-architect-author-infra/README.md +16 -0
- package/skills/temporal-architect-author-infra/SKILL.md +132 -0
- package/skills/temporal-architect-author-infra/reference/tcld.md +112 -0
- package/skills/temporal-architect-author-infra/reference/terraform.md +125 -0
- package/skills/temporal-architect-design/README.md +16 -0
- package/skills/temporal-architect-design/SKILL.md +224 -0
- package/skills/temporal-architect-design/reference/LANGUAGE.md +5 -0
- package/skills/temporal-architect-design/reference/anti-patterns.md +332 -0
- package/skills/temporal-architect-design/reference/common-errors.md +88 -0
- package/skills/temporal-architect-design/reference/core-principles.md +52 -0
- package/skills/temporal-architect-design/reference/design-checklist.md +59 -0
- package/skills/temporal-architect-design/reference/namespaces.md +84 -0
- package/skills/temporal-architect-design/reference/notation-examples.md +304 -0
- package/skills/temporal-architect-design/reference/notation-reference.md +70 -0
- package/skills/temporal-architect-design/reference/primitives-reference.md +65 -0
- package/skills/temporal-architect-design/reference/project-discovery-subagent.md +80 -0
- package/skills/temporal-architect-design/reference/reverse-engineering.md +53 -0
- package/skills/temporal-architect-design/reference/twf-conventions.md +43 -0
- package/skills/temporal-architect-design/reference/workflow-boundaries.md +43 -0
- package/skills/temporal-architect-design/topics/activities-advanced.md +358 -0
- package/skills/temporal-architect-design/topics/activities-advanced.twf +107 -0
- package/skills/temporal-architect-design/topics/child-workflows.md +347 -0
- package/skills/temporal-architect-design/topics/child-workflows.twf +171 -0
- package/skills/temporal-architect-design/topics/long-running.md +230 -0
- package/skills/temporal-architect-design/topics/long-running.twf +100 -0
- package/skills/temporal-architect-design/topics/nexus.md +248 -0
- package/skills/temporal-architect-design/topics/nexus.twf +148 -0
- package/skills/temporal-architect-design/topics/patterns.md +469 -0
- package/skills/temporal-architect-design/topics/patterns.twf +346 -0
- package/skills/temporal-architect-design/topics/promises-conditions.md +179 -0
- package/skills/temporal-architect-design/topics/promises-conditions.twf +213 -0
- package/skills/temporal-architect-design/topics/signals-queries-updates.md +319 -0
- package/skills/temporal-architect-design/topics/signals-queries-updates.twf +234 -0
- package/skills/temporal-architect-design/topics/task-queues.md +205 -0
- package/skills/temporal-architect-design/topics/task-queues.twf +184 -0
- package/skills/temporal-architect-design/topics/testing.md +437 -0
- package/skills/temporal-architect-design/topics/testing.twf +177 -0
- package/skills/temporal-architect-design/topics/timers-scheduling.md +131 -0
- package/skills/temporal-architect-design/topics/timers-scheduling.twf +129 -0
- package/skills/temporal-architect-design/topics/versioning.md +434 -0
- package/skills/temporal-architect-design/topics/versioning.twf +174 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
# TWF Notation Examples
|
|
2
|
+
|
|
3
|
+
## Basic Structure
|
|
4
|
+
|
|
5
|
+
A complete `.twf` file: workflows, activities, worker registration, and namespace deployment.
|
|
6
|
+
|
|
7
|
+
```twf
|
|
8
|
+
workflow WorkflowName(input: InputType) -> (OutputType):
|
|
9
|
+
activity ActivityName(input) -> result
|
|
10
|
+
workflow ChildWorkflowName(input) -> childResult
|
|
11
|
+
close complete(OutputType{result, childResult})
|
|
12
|
+
|
|
13
|
+
workflow ChildWorkflowName(input: InputType) -> (ChildResult):
|
|
14
|
+
activity DoWork(input) -> result
|
|
15
|
+
close complete(ChildResult{result})
|
|
16
|
+
|
|
17
|
+
activity ActivityName(input: InputType) -> (Result):
|
|
18
|
+
return process(input)
|
|
19
|
+
|
|
20
|
+
activity DoWork(input: InputType) -> (WorkResult):
|
|
21
|
+
return work(input)
|
|
22
|
+
|
|
23
|
+
worker mainWorker:
|
|
24
|
+
workflow WorkflowName
|
|
25
|
+
workflow ChildWorkflowName
|
|
26
|
+
activity ActivityName
|
|
27
|
+
activity DoWork
|
|
28
|
+
|
|
29
|
+
namespace default:
|
|
30
|
+
worker mainWorker
|
|
31
|
+
options:
|
|
32
|
+
task_queue: "main"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Activity Body Detail
|
|
36
|
+
|
|
37
|
+
Activity bodies are intentionally free-form (`raw_stmt`) — pseudocode or descriptive text representing SDK-level implementation. Every activity needs at least one statement (comments alone are not enough). Detail level depends on how obvious the behavior is from name and signature:
|
|
38
|
+
|
|
39
|
+
**Obvious** — minimal body:
|
|
40
|
+
|
|
41
|
+
```twf
|
|
42
|
+
activity SendEmail(to: string, body: string):
|
|
43
|
+
send(to, body)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
**Non-obvious** — comments describe intent, pseudocode anchors the body:
|
|
47
|
+
|
|
48
|
+
```twf
|
|
49
|
+
activity ExecuteToolCalls(toolCalls: ToolCalls) -> (ToolResults):
|
|
50
|
+
# Look up each tool by name in the tool registry
|
|
51
|
+
# Execute calls in parallel where possible
|
|
52
|
+
# If a tool is not found, return an error result (don't fail the activity)
|
|
53
|
+
registry.executeAll(toolCalls)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Complex contract** — describe error conditions, ordering, and idempotency:
|
|
57
|
+
|
|
58
|
+
```twf
|
|
59
|
+
activity ReconcileInventory(warehouseId: string, expected: Inventory) -> (ReconcileResult):
|
|
60
|
+
# Fetch current inventory, diff against expected, flag discrepancies
|
|
61
|
+
# Must be idempotent — running twice with same input produces same flags
|
|
62
|
+
# Warehouse API is rate-limited: max 10 requests/second
|
|
63
|
+
warehouse.reconcile(warehouseId, expected)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Control Flow
|
|
67
|
+
|
|
68
|
+
```twf
|
|
69
|
+
workflow ProcessOrder(order: Order) -> (Result):
|
|
70
|
+
activity ValidateOrder(order) -> validated
|
|
71
|
+
|
|
72
|
+
# Conditionals
|
|
73
|
+
if (validated.priority == "high"):
|
|
74
|
+
activity ExpediteOrder(order)
|
|
75
|
+
else:
|
|
76
|
+
activity StandardProcessing(order)
|
|
77
|
+
|
|
78
|
+
# Sequential loop — use for when each iteration depends on order or shared state
|
|
79
|
+
# For independent iterations, consider await all with parallel activities instead
|
|
80
|
+
for (item in order.items):
|
|
81
|
+
activity ProcessItem(item)
|
|
82
|
+
|
|
83
|
+
# Parallel execution — use await all when tasks are independent and all results needed
|
|
84
|
+
await all:
|
|
85
|
+
activity ReserveInventory(order) -> inventory
|
|
86
|
+
activity ProcessPayment(order) -> payment
|
|
87
|
+
|
|
88
|
+
close complete(Result{inventory, payment})
|
|
89
|
+
|
|
90
|
+
# Every referenced activity must be defined
|
|
91
|
+
activity ValidateOrder(order: Order) -> (ValidateResult):
|
|
92
|
+
return validate(order)
|
|
93
|
+
|
|
94
|
+
activity ExpediteOrder(order: Order):
|
|
95
|
+
expedite(order)
|
|
96
|
+
|
|
97
|
+
activity StandardProcessing(order: Order):
|
|
98
|
+
process(order)
|
|
99
|
+
|
|
100
|
+
activity ProcessItem(item: Item) -> (ItemResult):
|
|
101
|
+
return process(item)
|
|
102
|
+
|
|
103
|
+
activity ReserveInventory(order: Order) -> (Inventory):
|
|
104
|
+
return reserve(order)
|
|
105
|
+
|
|
106
|
+
activity ProcessPayment(order: Order) -> (Payment):
|
|
107
|
+
return charge(order)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Temporal Primitives in Notation
|
|
111
|
+
|
|
112
|
+
```twf
|
|
113
|
+
workflow OrderFulfillment(orderId: string) -> (OrderResult):
|
|
114
|
+
# Handlers go before body
|
|
115
|
+
# signal = write (fire-and-forget)
|
|
116
|
+
signal PaymentReceived(transactionId: string, amount: decimal):
|
|
117
|
+
paymentStatus = "received"
|
|
118
|
+
lastTransactionId = transactionId
|
|
119
|
+
|
|
120
|
+
# query = read (caller gets result)
|
|
121
|
+
query GetOrderStatus() -> (OrderStatus):
|
|
122
|
+
return OrderStatus{status: status, payment: paymentStatus}
|
|
123
|
+
|
|
124
|
+
# update = read-write (caller sends data, gets result)
|
|
125
|
+
update UpdateShippingAddress(address: Address) -> (Result):
|
|
126
|
+
activity ValidateAddress(address) -> validation
|
|
127
|
+
if (validation.valid):
|
|
128
|
+
shippingAddress = address
|
|
129
|
+
return Result{success: true}
|
|
130
|
+
else:
|
|
131
|
+
return Result{success: false, error: validation.reason}
|
|
132
|
+
|
|
133
|
+
# Workflow body starts after handlers
|
|
134
|
+
activity GetOrder(orderId) -> order
|
|
135
|
+
paymentStatus = "pending"
|
|
136
|
+
status = "awaiting_payment"
|
|
137
|
+
|
|
138
|
+
# Durable timer
|
|
139
|
+
await timer(1h)
|
|
140
|
+
|
|
141
|
+
# Wait for signal with timeout
|
|
142
|
+
await one:
|
|
143
|
+
signal PaymentReceived:
|
|
144
|
+
status = "processing"
|
|
145
|
+
timer(24h):
|
|
146
|
+
activity CancelOrder(orderId)
|
|
147
|
+
close fail(OrderResult{status: "cancelled"})
|
|
148
|
+
|
|
149
|
+
# Child workflow
|
|
150
|
+
workflow ShipOrder(order) -> shipResult
|
|
151
|
+
|
|
152
|
+
# Cross-namespace nexus call
|
|
153
|
+
nexus NotificationsEndpoint NotificationsService.SendNotification(order.customer, "shipped")
|
|
154
|
+
|
|
155
|
+
close complete(OrderResult{status: "completed"})
|
|
156
|
+
|
|
157
|
+
# Supporting definitions
|
|
158
|
+
activity GetOrder(orderId: string) -> (Order):
|
|
159
|
+
return db.get(orderId)
|
|
160
|
+
|
|
161
|
+
activity ValidateAddress(address: Address) -> (Validation):
|
|
162
|
+
return validate(address)
|
|
163
|
+
|
|
164
|
+
activity CancelOrder(orderId: string):
|
|
165
|
+
cancel(orderId)
|
|
166
|
+
|
|
167
|
+
workflow ShipOrder(order: Order) -> (ShipResult):
|
|
168
|
+
activity CreateShipment(order) -> shipment
|
|
169
|
+
close complete(ShipResult{shipment})
|
|
170
|
+
|
|
171
|
+
activity CreateShipment(order: Order) -> (Shipment):
|
|
172
|
+
return ship(order)
|
|
173
|
+
|
|
174
|
+
workflow SendNotification(customer: Customer, message: string):
|
|
175
|
+
activity Notify(customer, message)
|
|
176
|
+
close complete
|
|
177
|
+
|
|
178
|
+
activity Notify(customer: Customer, message: string):
|
|
179
|
+
send(customer, message)
|
|
180
|
+
|
|
181
|
+
nexus service NotificationsService:
|
|
182
|
+
async SendNotification workflow SendNotification
|
|
183
|
+
|
|
184
|
+
worker orderFulfillmentWorker:
|
|
185
|
+
workflow OrderFulfillment
|
|
186
|
+
workflow ShipOrder
|
|
187
|
+
workflow SendNotification
|
|
188
|
+
activity GetOrder
|
|
189
|
+
activity ValidateAddress
|
|
190
|
+
activity CancelOrder
|
|
191
|
+
activity CreateShipment
|
|
192
|
+
activity Notify
|
|
193
|
+
nexus service NotificationsService
|
|
194
|
+
|
|
195
|
+
namespace default:
|
|
196
|
+
worker orderFulfillmentWorker
|
|
197
|
+
options:
|
|
198
|
+
task_queue: "orderFulfillment"
|
|
199
|
+
nexus endpoint NotificationsEndpoint
|
|
200
|
+
options:
|
|
201
|
+
task_queue: "orderFulfillment"
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Async Patterns
|
|
205
|
+
|
|
206
|
+
```twf
|
|
207
|
+
workflow OrderPipeline(order: Order) -> (PipelineResult):
|
|
208
|
+
state:
|
|
209
|
+
condition paymentConfirmed
|
|
210
|
+
|
|
211
|
+
# Update handler — validates and confirms payment
|
|
212
|
+
update ConfirmPayment(txn: Transaction) -> (ConfirmResult):
|
|
213
|
+
activity ValidateTxn(txn) -> validation
|
|
214
|
+
if (validation.ok):
|
|
215
|
+
set paymentConfirmed
|
|
216
|
+
return ConfirmResult{accepted: true}
|
|
217
|
+
else:
|
|
218
|
+
return ConfirmResult{accepted: false, reason: validation.error}
|
|
219
|
+
|
|
220
|
+
# Promise — start async, await later
|
|
221
|
+
promise inventory <- activity CheckInventory(order)
|
|
222
|
+
|
|
223
|
+
# Detach — fire-and-forget, no result observation
|
|
224
|
+
detach workflow AuditLog(order)
|
|
225
|
+
|
|
226
|
+
# Await condition — blocks until handler sets it
|
|
227
|
+
await paymentConfirmed
|
|
228
|
+
|
|
229
|
+
# Await promise — get the result started earlier
|
|
230
|
+
await inventory -> stock
|
|
231
|
+
|
|
232
|
+
# Switch — multi-branch dispatch
|
|
233
|
+
switch (stock.level):
|
|
234
|
+
case "high":
|
|
235
|
+
activity ShipStandard(order) -> shipment
|
|
236
|
+
case "low":
|
|
237
|
+
activity ShipFromWarehouse(order, stock.warehouseId) -> shipment
|
|
238
|
+
case "none":
|
|
239
|
+
close fail(PipelineResult{error: "out of stock"})
|
|
240
|
+
|
|
241
|
+
close complete(PipelineResult{shipment})
|
|
242
|
+
|
|
243
|
+
# Heartbeat — report progress from long-running activity
|
|
244
|
+
activity ProcessLargeDataset(datasetId: string) -> (ProcessResult):
|
|
245
|
+
# Call heartbeat() periodically to report progress
|
|
246
|
+
# If worker dies, Temporal detects missed heartbeat and retries on another worker
|
|
247
|
+
heartbeat()
|
|
248
|
+
return process(datasetId)
|
|
249
|
+
|
|
250
|
+
# Call-level options — override timeout, routing, retry for a specific call
|
|
251
|
+
workflow DeployService(config: DeployConfig) -> (DeployResult):
|
|
252
|
+
activity BuildArtifact(config) -> artifact
|
|
253
|
+
activity Deploy(artifact) -> result
|
|
254
|
+
options:
|
|
255
|
+
start_to_close_timeout: 30m
|
|
256
|
+
heartbeat_timeout: 5m
|
|
257
|
+
retry_policy:
|
|
258
|
+
maximum_attempts: 3
|
|
259
|
+
close complete(DeployResult{result})
|
|
260
|
+
|
|
261
|
+
# Supporting definitions
|
|
262
|
+
activity CheckInventory(order: Order) -> (InventoryStatus):
|
|
263
|
+
return inventory.check(order)
|
|
264
|
+
|
|
265
|
+
activity ValidateTxn(txn: Transaction) -> (TxnValidation):
|
|
266
|
+
return payments.validate(txn)
|
|
267
|
+
|
|
268
|
+
workflow AuditLog(order: Order):
|
|
269
|
+
activity RecordAudit(order)
|
|
270
|
+
close complete
|
|
271
|
+
|
|
272
|
+
activity RecordAudit(order: Order):
|
|
273
|
+
audit.record(order)
|
|
274
|
+
|
|
275
|
+
activity ShipStandard(order: Order) -> (Shipment):
|
|
276
|
+
return shipping.standard(order)
|
|
277
|
+
|
|
278
|
+
activity ShipFromWarehouse(order: Order, warehouseId: string) -> (Shipment):
|
|
279
|
+
return shipping.fromWarehouse(order, warehouseId)
|
|
280
|
+
|
|
281
|
+
activity BuildArtifact(config: DeployConfig) -> (Artifact):
|
|
282
|
+
return build(config)
|
|
283
|
+
|
|
284
|
+
activity Deploy(artifact: Artifact) -> (DeployStatus):
|
|
285
|
+
return deploy(artifact)
|
|
286
|
+
|
|
287
|
+
worker pipelineWorker:
|
|
288
|
+
workflow OrderPipeline
|
|
289
|
+
workflow AuditLog
|
|
290
|
+
workflow DeployService
|
|
291
|
+
activity CheckInventory
|
|
292
|
+
activity ValidateTxn
|
|
293
|
+
activity RecordAudit
|
|
294
|
+
activity ShipStandard
|
|
295
|
+
activity ShipFromWarehouse
|
|
296
|
+
activity ProcessLargeDataset
|
|
297
|
+
activity BuildArtifact
|
|
298
|
+
activity Deploy
|
|
299
|
+
|
|
300
|
+
namespace default:
|
|
301
|
+
worker pipelineWorker
|
|
302
|
+
options:
|
|
303
|
+
task_queue: "pipeline"
|
|
304
|
+
```
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# TWF Notation Reference
|
|
2
|
+
|
|
3
|
+
| Syntax | Meaning |
|
|
4
|
+
|--------|---------|
|
|
5
|
+
| `activity Name(args) -> result` | Call activity, bind result (default for single operations) |
|
|
6
|
+
| `workflow Name(args) -> result` | Call child workflow, bind result (multi-step with own failure boundary) |
|
|
7
|
+
| `nexus Endpoint Service.Op(args) -> result` | Nexus service operation call |
|
|
8
|
+
| `detach nexus Endpoint Service.Op(args)` | Fire-and-forget nexus call (no result observation possible) |
|
|
9
|
+
| `promise p <- nexus Endpoint Service.Op(args)` | Start async nexus call |
|
|
10
|
+
| `promise p <- activity Name(args)` | Start async activity (use when you need the result later, not immediately) |
|
|
11
|
+
| `promise p <- workflow Name(args)` | Start async child workflow (parallel child execution) |
|
|
12
|
+
| `promise p <- timer(duration)` | Start async timer |
|
|
13
|
+
| `promise p <- signal Name` | Promise for signal |
|
|
14
|
+
| `await p -> result` | Await promise, bind result |
|
|
15
|
+
| `state:` | Workflow state block (conditions and variable initializations) |
|
|
16
|
+
| `condition name` | Named boolean awaitable (in `state:` block) |
|
|
17
|
+
| `set name` | Set condition to true (coordinate between handlers and main body) |
|
|
18
|
+
| `unset name` | Set condition to false |
|
|
19
|
+
| `await name` | Await condition |
|
|
20
|
+
| `detach workflow Name(args)` | Fire-and-forget child workflow (no result observation possible) |
|
|
21
|
+
| `await timer(duration)` | Durable sleep |
|
|
22
|
+
| `await signal Name` | Wait for signal |
|
|
23
|
+
| `await update Name` | Wait for update |
|
|
24
|
+
| `await nexus Endpoint Service.Op(args) -> result` | Wait for nexus call |
|
|
25
|
+
| `await one:` | Race: first to complete wins (timeouts, signal-or-timer patterns). Non-winning operations are **not** cancelled — they keep running until the workflow run ends |
|
|
26
|
+
| `await all:` | Join: wait for all (parallel execution) |
|
|
27
|
+
| `heartbeat()` | Report progress from long-running activity (detect worker death) |
|
|
28
|
+
| `options: key: value` | Options block for activity/workflow/nexus calls |
|
|
29
|
+
| `-> (Type)` | Return type (always parenthesized) |
|
|
30
|
+
| `-> result` | Bind preceding result |
|
|
31
|
+
| `close complete\|fail\|continue_as_new(Value)` | End workflow with result, failure, or continuation |
|
|
32
|
+
| `if (expr):` / `else:` | Conditional |
|
|
33
|
+
| `for (x in collection):` | Bounded loop |
|
|
34
|
+
| `for:` | Infinite loop (needs `close continue_as_new` or `close complete`) |
|
|
35
|
+
| `switch (expr):` / `case val:` | Multi-branch conditional |
|
|
36
|
+
| `close continue_as_new(args)` | Reset history and continue |
|
|
37
|
+
| `signal Name(params):` | Signal handler (in workflow, before body) |
|
|
38
|
+
| `query Name(params) -> (Type):` | Query handler (in workflow, before body) |
|
|
39
|
+
| `update Name(params) -> (Type):` | Update handler (in workflow, before body) |
|
|
40
|
+
| `nexus service Name:` | Nexus service definition (top-level) |
|
|
41
|
+
| `async OpName workflow WorkflowName` | Async nexus operation (in service body) |
|
|
42
|
+
| `sync OpName(params) -> (Type):` | Sync nexus operation (in service body) |
|
|
43
|
+
| `worker name:` | Worker type set definition |
|
|
44
|
+
| `nexus service Name` (in worker) | Register nexus service on worker |
|
|
45
|
+
| `namespace name:` | Namespace definition (deployment with options) |
|
|
46
|
+
| `nexus endpoint Name` (in namespace) | Nexus endpoint instantiation with task_queue |
|
|
47
|
+
|
|
48
|
+
## Common `options:` Keys
|
|
49
|
+
|
|
50
|
+
`options:` blocks attach operational config to a call. A clean `twf check` does not require any of these — but the design must reason about them (idempotency, history cost, failure behavior, routing). This is a lookup table; the *reasoning* lives in the worked example in [SKILL.md](../SKILL.md#design-flow) and the topic docs.
|
|
51
|
+
|
|
52
|
+
| Key | Attaches to | Why it matters |
|
|
53
|
+
|-----|-------------|----------------|
|
|
54
|
+
| `task_queue` | activity, workflow | Routing — pins the call to a specific worker pool (capability, isolation, region) |
|
|
55
|
+
| `start_to_close_timeout` | activity | Failure behavior — bounds a single attempt; required in practice for any real activity |
|
|
56
|
+
| `schedule_to_close_timeout` | activity, nexus | Total time budget across queueing + attempts |
|
|
57
|
+
| `schedule_to_start_timeout` | activity | Tolerance for queue wait before a worker picks it up |
|
|
58
|
+
| `heartbeat_timeout` | activity | Worker-death detection for long activities (pairs with `heartbeat()`) |
|
|
59
|
+
| `retry_policy` | activity, workflow, nexus | Failure behavior — `initial_interval`, `backoff_coefficient`, `maximum_interval`, `maximum_attempts`, `non_retryable_error_types` |
|
|
60
|
+
| `workflow_execution_timeout` / `workflow_run_timeout` / `workflow_task_timeout` | workflow | Bounds for total / per-run / per-task duration |
|
|
61
|
+
| `parent_close_policy` | workflow | Child lifecycle when parent closes: `TERMINATE` (default), `REQUEST_CANCEL`, `ABANDON` |
|
|
62
|
+
| `workflow_id_reuse_policy` | workflow | Idempotency on retry: `ALLOW_DUPLICATE`, `ALLOW_DUPLICATE_FAILED_ONLY`, `REJECT_DUPLICATE`, `TERMINATE_IF_RUNNING` |
|
|
63
|
+
| `cron_schedule` | workflow | Recurring child execution |
|
|
64
|
+
| `priority` | activity, workflow, nexus | Relative dispatch priority |
|
|
65
|
+
|
|
66
|
+
> The child-workflow **ID** itself is an SDK-level concern, not a TWF call option — see [child-workflows.md](../topics/child-workflows.md#workflow-id-design). `task_queue` is intentionally **not** a nexus-call option; nexus routing comes from the endpoint declaration.
|
|
67
|
+
|
|
68
|
+
> **Worker-instantiation `options:` are a separate set** from the call options above — they attach to a `worker` (or `nexus endpoint`) inside a `namespace`, not to a call. `task_queue` is required; `versioning` (`none` / `build_id` / `deployment`) is the design-altitude strategy key. The set is the SDK union, accepted permissively — see [task-queues.md](../topics/task-queues.md#worker-options) and `twf spec`.
|
|
69
|
+
|
|
70
|
+
Full grammar: [`tools/spec/sections/`](../../../tools/spec/sections/) (or run `twf spec`).
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Temporal Primitives Reference
|
|
2
|
+
|
|
3
|
+
## Workflow Execution
|
|
4
|
+
|
|
5
|
+
| Primitive | Purpose | Details |
|
|
6
|
+
|-----------|---------|---------|
|
|
7
|
+
| `activity` | Side-effecting operation | Core primitive |
|
|
8
|
+
| `workflow` | Child workflow | [child-workflows.md](../topics/child-workflows.md) |
|
|
9
|
+
| `nexus` | Cross-namespace call | [nexus.md](../topics/nexus.md) |
|
|
10
|
+
| `promise` | Async operation, await later | [promises-conditions.md](../topics/promises-conditions.md) |
|
|
11
|
+
| `detach` | Fire-and-forget child/nexus | [child-workflows.md](../topics/child-workflows.md), [nexus.md](../topics/nexus.md) |
|
|
12
|
+
| `close continue_as_new` | Reset history, continue | [long-running.md](../topics/long-running.md) |
|
|
13
|
+
|
|
14
|
+
**Selection:** `activity` for single side-effecting operations. `workflow` for multi-step orchestration needing its own retry/failure boundary. `nexus` when crossing namespace or team boundaries. `promise` when you need the result later, not immediately. `detach` for fire-and-forget — you cannot observe the result. `close continue_as_new` when history grows unbounded (long-running or entity workflows).
|
|
15
|
+
|
|
16
|
+
## Timing
|
|
17
|
+
|
|
18
|
+
| Primitive | Purpose | Details |
|
|
19
|
+
|-----------|---------|---------|
|
|
20
|
+
| `timer` | Durable sleep (survives restarts) | [timers-scheduling.md](../topics/timers-scheduling.md) |
|
|
21
|
+
| `schedule` | Cron-like recurring execution | [timers-scheduling.md](../topics/timers-scheduling.md) |
|
|
22
|
+
| `timeout` | Deadline for operations | [timers-scheduling.md](../topics/timers-scheduling.md) |
|
|
23
|
+
|
|
24
|
+
**Selection:** `timer` for durable waits inside workflow logic (survives replay). Activity-level timeouts (`heartbeat_timeout`, `start_to_close_timeout` in `options:`) for bounding activity execution. `schedule` for cron-like recurring workflow starts — this is platform configuration, not TWF notation.
|
|
25
|
+
|
|
26
|
+
## External Communication
|
|
27
|
+
|
|
28
|
+
Read, write, or read-write interaction with a running workflow:
|
|
29
|
+
|
|
30
|
+
| Primitive | I/O | Purpose | Details |
|
|
31
|
+
|-----------|-----|---------|---------|
|
|
32
|
+
| `query` | Read | Sync read of workflow state | [signals-queries-updates.md](../topics/signals-queries-updates.md) |
|
|
33
|
+
| `signal` | Write | Async fire-and-forget into workflow | [signals-queries-updates.md](../topics/signals-queries-updates.md) |
|
|
34
|
+
| `update` | Read-write | Sync mutation with result | [signals-queries-updates.md](../topics/signals-queries-updates.md) |
|
|
35
|
+
|
|
36
|
+
**Selection:** Use I/O direction as the decision rule. **Do not** use `query` to modify state — queries must be pure reads. **Do not** use `signal` when you need confirmation — signals have no return value. Prefer `update` over signal-then-query when the caller needs to know the mutation succeeded.
|
|
37
|
+
|
|
38
|
+
## State and Conditions
|
|
39
|
+
|
|
40
|
+
| Primitive | Purpose | Details |
|
|
41
|
+
|-----------|---------|---------|
|
|
42
|
+
| `state` | Workflow state declaration block | [promises-conditions.md](../topics/promises-conditions.md) |
|
|
43
|
+
| `condition` | Named boolean awaitable | [promises-conditions.md](../topics/promises-conditions.md) |
|
|
44
|
+
| `set` / `unset` | Set condition to true / false | [promises-conditions.md](../topics/promises-conditions.md) |
|
|
45
|
+
|
|
46
|
+
**Selection:** Use `condition` when handlers and the main workflow body need to coordinate on a boolean flag (e.g., "payment received", "approved"). Use local variables for workflow-scoped state that doesn't need cross-handler coordination.
|
|
47
|
+
|
|
48
|
+
## Activity Options
|
|
49
|
+
|
|
50
|
+
| Primitive | Purpose | Details |
|
|
51
|
+
|-----------|---------|---------|
|
|
52
|
+
| `heartbeat` | Report progress, detect worker death | [activities-advanced.md](../topics/activities-advanced.md) |
|
|
53
|
+
| `async_complete` | Complete from external system | [activities-advanced.md](../topics/activities-advanced.md) |
|
|
54
|
+
|
|
55
|
+
## Infrastructure
|
|
56
|
+
|
|
57
|
+
| Primitive | Purpose | Details |
|
|
58
|
+
|-----------|---------|---------|
|
|
59
|
+
| `task_queue` | Route work to specific workers | [task-queues.md](../topics/task-queues.md) |
|
|
60
|
+
| `worker` | Defines a reusable type set (which workflows, activities, nexus services run together) | [task-queues.md](../topics/task-queues.md) |
|
|
61
|
+
| `namespace` | Deployment topology — instantiates workers with `task_queue` and options | [task-queues.md](../topics/task-queues.md) |
|
|
62
|
+
| `nexus service` | Typed operation group for cross-namespace calls | [nexus.md](../topics/nexus.md) |
|
|
63
|
+
| `nexus endpoint` | Routes nexus calls to a target task queue | [nexus.md](../topics/nexus.md) |
|
|
64
|
+
| `search_attribute` | Index workflow for queries | Core primitive |
|
|
65
|
+
| `memo` | Attach metadata to workflow | Core primitive |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Project-Discovery Subagent
|
|
2
|
+
|
|
3
|
+
A single reusable primitive: **scan an existing repo on a bounded slice, return a compact summary.** Owned here (design-skill) and shared by every skill that has to understand a repo it didn't write:
|
|
4
|
+
|
|
5
|
+
- **design reverse-engineering** — [reverse-engineering.md](./reverse-engineering.md) B1a bootstrap (no `.twf`) and B1b drift-check.
|
|
6
|
+
- **author-go existing-repo Orient** — `temporal-architect-author-go/SKILL.md` (see `author-go-skill/REVISIONS_001` Group 1).
|
|
7
|
+
- **author-infra** — repo/tooling discovery when that skill lands.
|
|
8
|
+
|
|
9
|
+
Design once, reuse. This spec is the broadened, shared form of the `sdk-explorer` agent sketched in [SUBAGENT_ADOPTION.md](../../temporal-architect-author-go/SUBAGENT_ADOPTION.md) — it answers that doc's open question ("should it also scan the user's existing project code for conventions?") with **yes**: project-convention discovery is this subagent's job, not the orchestrator's.
|
|
10
|
+
|
|
11
|
+
## Why a subagent
|
|
12
|
+
|
|
13
|
+
Discovery is context-heavy and disposable. Reading a repo's tooling, layout, and registration wiring burns tokens the design conversation needs — and almost none of what it reads belongs in the main context, only the conclusions. Running discovery in an **isolated subagent that returns a summary** is the context-protection move: the orchestrator stays focused on design, the subagent absorbs the noise.
|
|
14
|
+
|
|
15
|
+
## Trigger discipline
|
|
16
|
+
|
|
17
|
+
Dispatch **deliberately, on a bounded slice** — never reflexively, never "scan the whole repo." The caller names the slice (a domain, a directory, a set of entry points). An unbounded scan defeats the purpose: it floods the subagent's own context and returns a summary too broad to act on. If the slice is unclear, the orchestrator narrows it *with the user* before dispatching.
|
|
18
|
+
|
|
19
|
+
## Inputs
|
|
20
|
+
|
|
21
|
+
| Input | Description |
|
|
22
|
+
|-------|-------------|
|
|
23
|
+
| Repo root | Absolute path to the project. |
|
|
24
|
+
| Bounded slice | The paths / domain / entry points to scan. Required — no whole-repo scans. |
|
|
25
|
+
| Focus | What the caller needs (e.g. "Temporal registration style", "codegen tooling", "existing `.twf` for this domain"). |
|
|
26
|
+
|
|
27
|
+
## What it scans
|
|
28
|
+
|
|
29
|
+
- **Build / codegen tooling** — `Makefile`, `buf.yaml` / `buf.gen.yaml`, `//go:generate` directives, `protoc` plugins. Identifies whether code is hand-written or generated, and by what.
|
|
30
|
+
- **Package layout** — directory structure, where workflows/activities live, `proto/` → `gen/` → `lib/` style splits.
|
|
31
|
+
- **Temporal SDK usage** — `go.temporal.io/sdk` imports, `workflow.ExecuteActivity` / `ExecuteChildWorkflow` / Nexus call sites, signal/query/update handlers.
|
|
32
|
+
- **Registration style** — `worker.New` + `RegisterWorkflow`/`RegisterActivity`, generated `RegisterXxxActivities/Workflows` helpers, DI wiring (`fx`), struct-vs-func activities.
|
|
33
|
+
- **`.twf` / `.tf` presence** — existing design files and Terraform/infra files for this slice.
|
|
34
|
+
- **Comment conventions** — impl-link headers and cross-domain stub markers (see [twf-conventions.md](./twf-conventions.md)); these point discovery straight at the implementation.
|
|
35
|
+
|
|
36
|
+
## Output
|
|
37
|
+
|
|
38
|
+
A **compact structured summary** — conclusions, not raw dumps. Never paste whole files back. Cover:
|
|
39
|
+
|
|
40
|
+
- Tooling: hand-written vs generated; generator stack if any.
|
|
41
|
+
- Layout: where the relevant code lives for the scanned slice.
|
|
42
|
+
- SDK usage: workflows/activities/nexus found, with their call sites.
|
|
43
|
+
- Registration: how workers register the discovered types.
|
|
44
|
+
- Existing `.twf`: present/absent, and what it covers.
|
|
45
|
+
- Impl-links: any comment conventions found, and what they point to.
|
|
46
|
+
- Open questions the caller must resolve.
|
|
47
|
+
|
|
48
|
+
## Agent definition
|
|
49
|
+
|
|
50
|
+
Copy-pastable subagent prompt (frontmatter style matches `sdk-explorer` in [SUBAGENT_ADOPTION.md](../../temporal-architect-author-go/SUBAGENT_ADOPTION.md)):
|
|
51
|
+
|
|
52
|
+
```yaml
|
|
53
|
+
---
|
|
54
|
+
name: project-discovery
|
|
55
|
+
description: Scan an existing repo on a bounded slice for tooling, layout, conventions, and Temporal usage. Returns a compact summary.
|
|
56
|
+
tools: Read, Glob, Grep, Bash, WebFetch, WebSearch
|
|
57
|
+
model: sonnet
|
|
58
|
+
---
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
You are scanning an existing repository on a BOUNDED SLICE. Do not scan the whole
|
|
63
|
+
repo — stay within the slice the caller named.
|
|
64
|
+
|
|
65
|
+
Inputs:
|
|
66
|
+
- Repo root: <path>
|
|
67
|
+
- Slice: <paths / domain / entry points>
|
|
68
|
+
- Focus: <what the caller needs>
|
|
69
|
+
|
|
70
|
+
Scan for: build/codegen tooling (Makefile, buf.gen.yaml, //go:generate), package
|
|
71
|
+
layout, Temporal SDK usage (workflow/activity/nexus call sites, handlers),
|
|
72
|
+
registration style (worker.New + Register*, generated helpers, fx wiring), .twf/.tf
|
|
73
|
+
presence, and impl-link / cross-domain-stub comment conventions.
|
|
74
|
+
|
|
75
|
+
Return a COMPACT STRUCTURED SUMMARY — conclusions only, never raw file dumps:
|
|
76
|
+
tooling, layout, SDK usage, registration, existing .twf, impl-links, open questions.
|
|
77
|
+
|
|
78
|
+
For SDK-symbol meaning, delegate to the relevant author skill's reference (e.g.
|
|
79
|
+
author-go references read backward) rather than reconstructing it yourself.
|
|
80
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Reverse Engineering: Code → `.twf`
|
|
2
|
+
|
|
3
|
+
The dominant adoption path is not greenfield. Most teams already run Temporal code and want a `.twf` that captures what they have — to review it, refactor it, or hand it to the visualizer. Here the **implementation is the requirement** and the `.twf` is recovered from it.
|
|
4
|
+
|
|
5
|
+
Treat the recovered `.twf` as the **central, living design medium** — the artifact the team will keep editing and reviewing — not a throwaway sketch of the code. That framing sets the bar: capture the design faithfully enough that it's worth keeping.
|
|
6
|
+
|
|
7
|
+
This is a **parallel path** to the greenfield loop, kept separate on purpose. The main [SKILL.md](../SKILL.md) body carries only a thin trigger; the mechanics live here so the fast forward loop never pays for them. Discovery runs in an isolated [project-discovery subagent](./project-discovery-subagent.md) and returns a summary — the context-protection move.
|
|
8
|
+
|
|
9
|
+
## Trigger discipline
|
|
10
|
+
|
|
11
|
+
Reverse-engineer **deliberately, on a bounded slice** — one domain, one service, one set of entry points at a time. Never scan the whole repo reflexively. A slice keeps both the discovery subagent and the resulting `.twf` reviewable; a whole-repo sweep produces a `.twf` nobody can check.
|
|
12
|
+
|
|
13
|
+
## Two cases
|
|
14
|
+
|
|
15
|
+
### B1a — Bootstrap (no `.twf` yet)
|
|
16
|
+
|
|
17
|
+
The repo has Temporal code but no design file. Recover one from scratch:
|
|
18
|
+
|
|
19
|
+
1. **Discover** — dispatch the [project-discovery subagent](./project-discovery-subagent.md) on the bounded slice. It returns tooling, layout, SDK usage, registration style, and any impl-link comments.
|
|
20
|
+
2. **Extract** — translate the discovered workflows/activities/nexus into `.twf` (see [Reading strategy](#reading-strategy)).
|
|
21
|
+
3. **Fidelity check** — confirm the `.twf` matches what the code actually does (see [Fidelity first](#fidelity-first-then-design-review)).
|
|
22
|
+
4. **Design Review** — only now run the standard [Design Review](../SKILL.md#design-review).
|
|
23
|
+
|
|
24
|
+
Write the [impl-link header](./twf-conventions.md) as you extract, so the new `.twf` records where its implementation lives.
|
|
25
|
+
|
|
26
|
+
### B1b — Drift / sync (`.twf` exists but is stale)
|
|
27
|
+
|
|
28
|
+
A `.twf` exists but the code has moved on. Two halves, with different readiness:
|
|
29
|
+
|
|
30
|
+
- **Check (available now):** on a bounded slice, compare the `.twf` against current code and report divergences — missing activities, changed boundaries, dropped signals. This needs no new tooling.
|
|
31
|
+
- **Sync (deferred):** mechanically reconciling `.twf` ↔ implementation depends on the future twf↔impl mapping (`dsl/BACKLOG.md` → Reference Annotations / `@ref`). Until that lands, reconcile by hand: re-extract the drifted slice (B1a steps 2-4) and update the `.twf`, treating the code as the source of truth for *behavior* and the existing `.twf` as the source of truth for *intent*.
|
|
32
|
+
|
|
33
|
+
## Reading strategy
|
|
34
|
+
|
|
35
|
+
Read for **design structure**, not line-by-line behavior:
|
|
36
|
+
|
|
37
|
+
- **Find entry points first** — client-started workflows, schedule-started workflows, Nexus-operation-backing workflows, handler-bearing workflows. These are the roots of the `.twf`.
|
|
38
|
+
- **Follow call sites** — `workflow.ExecuteActivity`, `workflow.ExecuteChildWorkflow`, Nexus operation calls. Each is an `activity` / `workflow` / `nexus` call in TWF.
|
|
39
|
+
- **Ignore the plumbing** — error wrapping, `context` threading, logging, options structs, retry/timeout boilerplate. None of it changes the design shape; capture only the options that express a real decision (a tuned timeout, a capped retry).
|
|
40
|
+
- **Detect parallelism** — `workflow.Go`, selectors (`workflow.NewSelector`), futures held and `.Get` later → `await all` / `await one` / `promise` in TWF. Sequential `.Get` right after the call is just a synchronous call.
|
|
41
|
+
|
|
42
|
+
### Delegate SDK reading to the author skill
|
|
43
|
+
|
|
44
|
+
Do not reconstruct SDK semantics yourself. The author skills already hold the DSL↔SDK mapping — **read their symbol tables backward.** For Go, use the `temporal-architect-author-go` references (e.g. `activity-call.md`, `workflow-call.md`, `await-all.md`) and, for generated code, its forthcoming `reference/proto-driven.md` Rosetta Stone (generated `XxxActivities` iface, `RegisterXxxActivities`, `XxxFuture`, etc. → the underlying `activity`/`workflow`). Forward mappings (DSL → Go) read in reverse give you Go → DSL for free.
|
|
45
|
+
|
|
46
|
+
## Fidelity first, then Design Review
|
|
47
|
+
|
|
48
|
+
**Capture what the code does, faithfully — including its anti-patterns.** A wrapper workflow, a monolith, an unbounded loop: extract them *as they are*. The recovered `.twf` must mirror reality before it's worth reviewing; silently "fixing" during extraction produces a `.twf` that describes a system that doesn't exist, and hides the very problems the team needs to see.
|
|
49
|
+
|
|
50
|
+
- **Do not** refactor, rename, or "improve" during extraction.
|
|
51
|
+
- **Intent-fill only genuinely-unimplemented stubs** — a `// TODO` body, a panic-not-implemented, an empty handler. Mark these clearly; everything else is recovered, not invented.
|
|
52
|
+
|
|
53
|
+
Once the `.twf` faithfully reflects the code, run the standard [Design Review](../SKILL.md#design-review). That pass is where anti-patterns get *named and proposed for change* — separately from extraction, so the record of "what exists" stays honest and the "what should change" is an explicit, reviewable diff.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# `.twf` Conventions
|
|
2
|
+
|
|
3
|
+
How to lay out `.twf` files and the small set of comment conventions that carry information the grammar doesn't yet express.
|
|
4
|
+
|
|
5
|
+
## One package for all `.twf`
|
|
6
|
+
|
|
7
|
+
**Recommendation: keep all of a project's `.twf` in one package** (one directory whose files resolve together). Cross-file references — an `activity` defined in one file and called in another, a `nexus service` provided in one file and called in another — resolve only within a shared file set. One package means every reference resolves and `twf check` sees the whole design at once.
|
|
8
|
+
|
|
9
|
+
This decouples `.twf` layout from code layout: the files no longer mirror the directory structure of the implementation. That trade-off is worth it — resolution coverage matters more than co-location — and the [impl-link header](#impl-link-header) below restores the link to the code.
|
|
10
|
+
|
|
11
|
+
## Comment conventions
|
|
12
|
+
|
|
13
|
+
`.twf` comments (`#`) are free text to the parser, but a small **named set** carries conventional meaning the tooling and the reader rely on. Use these exact forms.
|
|
14
|
+
|
|
15
|
+
### Impl-link header
|
|
16
|
+
|
|
17
|
+
A top-of-file comment linking a `.twf` to the implementation directory (or directories) it describes:
|
|
18
|
+
|
|
19
|
+
```twf
|
|
20
|
+
# impl: order-service/workflows, order-service/activities
|
|
21
|
+
workflow ProcessOrder(order: Order) -> (Result):
|
|
22
|
+
activity ChargePayment(order) -> receipt
|
|
23
|
+
close complete(Result{receipt})
|
|
24
|
+
|
|
25
|
+
activity ChargePayment(order: Order) -> (Receipt):
|
|
26
|
+
charge(order.payment)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Because the one-package layout decouples `.twf` from code, the implementation link can no longer be inferred from location — it has to be explicit. The header is that link. It is also a [reverse-engineering](./reverse-engineering.md) aid: the [project-discovery subagent](./project-discovery-subagent.md) *reads* it to jump straight to the code, and extraction *writes* it when recovering a `.twf` from existing code.
|
|
30
|
+
|
|
31
|
+
This is the interim form. The durable, machine-checkable version is per-symbol reference annotations (`@ref`), deferred in `dsl/BACKLOG.md` — when that lands, the header convention gives way to it.
|
|
32
|
+
|
|
33
|
+
### Cross-domain stub marker
|
|
34
|
+
|
|
35
|
+
A marker on a local stub definition that exists only to satisfy resolution for a symbol actually defined in another `.twf`:
|
|
36
|
+
|
|
37
|
+
```twf
|
|
38
|
+
# cross-domain stub — defined in payments.twf
|
|
39
|
+
nexus service PaymentService:
|
|
40
|
+
sync ChargeCard(req: ChargeRequest) -> (Receipt)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Defining *one* local `nexus service` turns every other service reference in the file into a hard error — including genuinely external services in other namespaces (see [common-errors.md](./common-errors.md#nexus-resolution-external-warning-vs-local-error)). The stub marker documents that the definition is a local placeholder, not the real owner, so a partial / per-domain file can both call and provide services without tripping the resolution cliff. The marker makes the stub's intent obvious to readers and to the discovery subagent.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Workflow vs Activity Boundary
|
|
2
|
+
|
|
3
|
+
## Use Activities When
|
|
4
|
+
|
|
5
|
+
**Default granularity: one activity per network call / external interaction.** This is the right default because it makes Temporal's retry, timeout, and backoff land at exactly the unit that can fail independently — one activity per fallible boundary gives clean, granular retry semantics. Start here, then deviate only as an optimization (batching, local activities — see [core-principles.md](./core-principles.md#activities-are-for-io--not-in-memory-work)).
|
|
6
|
+
|
|
7
|
+
- Single atomic operation
|
|
8
|
+
- External system interaction (API, DB, file)
|
|
9
|
+
- Short, predictable completion time (single timeout period)
|
|
10
|
+
- No orchestration logic
|
|
11
|
+
|
|
12
|
+
Conversely, do **not** create an activity for work that touches no external system — reads of already-held data, in-memory derivation, or accumulation are workflow code, not activities.
|
|
13
|
+
|
|
14
|
+
## Use Child Workflows When
|
|
15
|
+
|
|
16
|
+
- Multiple steps with independent retry/timeout policies
|
|
17
|
+
- Reusable across parent workflows
|
|
18
|
+
- Separate failure boundary needed
|
|
19
|
+
- Very long operations (separate history)
|
|
20
|
+
- Complex enough to warrant own tests
|
|
21
|
+
|
|
22
|
+
**Rule of thumb:** Loops or conditionals inside an activity → should be a workflow.
|
|
23
|
+
|
|
24
|
+
## Use Nexus When
|
|
25
|
+
|
|
26
|
+
- Crosses namespace or team boundaries (separate deployment lifecycle)
|
|
27
|
+
- Different team owns the target service
|
|
28
|
+
- Target needs independent scaling, versioning, or failure isolation at the organizational level
|
|
29
|
+
- You want a typed API contract between services
|
|
30
|
+
|
|
31
|
+
**Child workflow vs nexus:** Child workflows share a namespace and are tightly coupled to the parent's lifecycle. Nexus calls are loosely coupled — the target is an independent service that may be owned by another team, deployed on a different schedule, or running in a different namespace.
|
|
32
|
+
|
|
33
|
+
## Common Mistakes
|
|
34
|
+
|
|
35
|
+
**Wrapper workflow:** A child workflow containing a single activity call adds orchestration overhead with no benefit. If there's only one step, use an activity directly.
|
|
36
|
+
|
|
37
|
+
**Monolithic workflow:** All logic in one workflow with hundreds of history events. If a workflow has more than ~10 sequential activity calls, consider decomposing into child workflows.
|
|
38
|
+
|
|
39
|
+
**Activity with orchestration:** If an activity contains retry logic, conditional branching, or calls to other services, it should be a workflow — these are orchestration concerns that benefit from Temporal's durability.
|
|
40
|
+
|
|
41
|
+
See [child-workflows.md](../topics/child-workflows.md) for detailed child workflow patterns and the full decision table.
|
|
42
|
+
|
|
43
|
+
For deployment topology and task queue routing, see [task-queues.md](../topics/task-queues.md).
|