@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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +38 -0
  3. package/package.json +37 -0
  4. package/skills/MANIFEST.json +373 -0
  5. package/skills/MANIFEST.md +121 -0
  6. package/skills/temporal-architect/SKILL.md +99 -0
  7. package/skills/temporal-architect/reference/decomposition.md +78 -0
  8. package/skills/temporal-architect-author-go/README.md +16 -0
  9. package/skills/temporal-architect-author-go/SKILL.md +191 -0
  10. package/skills/temporal-architect-author-go/SUBAGENT_ADOPTION.md +161 -0
  11. package/skills/temporal-architect-author-go/reference/activity-call.md +73 -0
  12. package/skills/temporal-architect-author-go/reference/activity-def.md +54 -0
  13. package/skills/temporal-architect-author-go/reference/assignment.md +36 -0
  14. package/skills/temporal-architect-author-go/reference/await-all.md +104 -0
  15. package/skills/temporal-architect-author-go/reference/await-one.md +193 -0
  16. package/skills/temporal-architect-author-go/reference/await-timer.md +35 -0
  17. package/skills/temporal-architect-author-go/reference/close.md +71 -0
  18. package/skills/temporal-architect-author-go/reference/composite-patterns.md +176 -0
  19. package/skills/temporal-architect-author-go/reference/condition.md +56 -0
  20. package/skills/temporal-architect-author-go/reference/control-flow.md +151 -0
  21. package/skills/temporal-architect-author-go/reference/dependency-resolution.md +29 -0
  22. package/skills/temporal-architect-author-go/reference/detach.md +52 -0
  23. package/skills/temporal-architect-author-go/reference/heartbeat.md +84 -0
  24. package/skills/temporal-architect-author-go/reference/nexus-service-def.md +73 -0
  25. package/skills/temporal-architect-author-go/reference/nexus.md +35 -0
  26. package/skills/temporal-architect-author-go/reference/options.md +138 -0
  27. package/skills/temporal-architect-author-go/reference/promise.md +73 -0
  28. package/skills/temporal-architect-author-go/reference/proto-driven.md +197 -0
  29. package/skills/temporal-architect-author-go/reference/query-handler.md +34 -0
  30. package/skills/temporal-architect-author-go/reference/signal-handler.md +73 -0
  31. package/skills/temporal-architect-author-go/reference/three-layer-testing.md +173 -0
  32. package/skills/temporal-architect-author-go/reference/types.md +72 -0
  33. package/skills/temporal-architect-author-go/reference/update-handler.md +64 -0
  34. package/skills/temporal-architect-author-go/reference/worker.md +215 -0
  35. package/skills/temporal-architect-author-go/reference/workflow-call.md +37 -0
  36. package/skills/temporal-architect-author-go/reference/workflow-def.md +45 -0
  37. package/skills/temporal-architect-author-infra/README.md +16 -0
  38. package/skills/temporal-architect-author-infra/SKILL.md +132 -0
  39. package/skills/temporal-architect-author-infra/reference/tcld.md +112 -0
  40. package/skills/temporal-architect-author-infra/reference/terraform.md +125 -0
  41. package/skills/temporal-architect-design/README.md +16 -0
  42. package/skills/temporal-architect-design/SKILL.md +224 -0
  43. package/skills/temporal-architect-design/reference/LANGUAGE.md +5 -0
  44. package/skills/temporal-architect-design/reference/anti-patterns.md +332 -0
  45. package/skills/temporal-architect-design/reference/common-errors.md +88 -0
  46. package/skills/temporal-architect-design/reference/core-principles.md +52 -0
  47. package/skills/temporal-architect-design/reference/design-checklist.md +59 -0
  48. package/skills/temporal-architect-design/reference/namespaces.md +84 -0
  49. package/skills/temporal-architect-design/reference/notation-examples.md +304 -0
  50. package/skills/temporal-architect-design/reference/notation-reference.md +70 -0
  51. package/skills/temporal-architect-design/reference/primitives-reference.md +65 -0
  52. package/skills/temporal-architect-design/reference/project-discovery-subagent.md +80 -0
  53. package/skills/temporal-architect-design/reference/reverse-engineering.md +53 -0
  54. package/skills/temporal-architect-design/reference/twf-conventions.md +43 -0
  55. package/skills/temporal-architect-design/reference/workflow-boundaries.md +43 -0
  56. package/skills/temporal-architect-design/topics/activities-advanced.md +358 -0
  57. package/skills/temporal-architect-design/topics/activities-advanced.twf +107 -0
  58. package/skills/temporal-architect-design/topics/child-workflows.md +347 -0
  59. package/skills/temporal-architect-design/topics/child-workflows.twf +171 -0
  60. package/skills/temporal-architect-design/topics/long-running.md +230 -0
  61. package/skills/temporal-architect-design/topics/long-running.twf +100 -0
  62. package/skills/temporal-architect-design/topics/nexus.md +248 -0
  63. package/skills/temporal-architect-design/topics/nexus.twf +148 -0
  64. package/skills/temporal-architect-design/topics/patterns.md +469 -0
  65. package/skills/temporal-architect-design/topics/patterns.twf +346 -0
  66. package/skills/temporal-architect-design/topics/promises-conditions.md +179 -0
  67. package/skills/temporal-architect-design/topics/promises-conditions.twf +213 -0
  68. package/skills/temporal-architect-design/topics/signals-queries-updates.md +319 -0
  69. package/skills/temporal-architect-design/topics/signals-queries-updates.twf +234 -0
  70. package/skills/temporal-architect-design/topics/task-queues.md +205 -0
  71. package/skills/temporal-architect-design/topics/task-queues.twf +184 -0
  72. package/skills/temporal-architect-design/topics/testing.md +437 -0
  73. package/skills/temporal-architect-design/topics/testing.twf +177 -0
  74. package/skills/temporal-architect-design/topics/timers-scheduling.md +131 -0
  75. package/skills/temporal-architect-design/topics/timers-scheduling.twf +129 -0
  76. package/skills/temporal-architect-design/topics/versioning.md +434 -0
  77. package/skills/temporal-architect-design/topics/versioning.twf +174 -0
@@ -0,0 +1,332 @@
1
+ # Common Anti-Patterns
2
+
3
+ > **Re-check pass (required).** This catalog is not just reference — during the [Design Review](../SKILL.md#design-review), walk the *finished* design against **every** anti-pattern below and confirm none apply. Designs drift into these shapes by copying prior work uncritically; pattern-matching against a catalog of bad shapes is exactly what this pass is for. The checklist line is: "Design re-checked against every anti-pattern in `anti-patterns.md`."
4
+
5
+ ## Structural
6
+
7
+ ### Unbounded History
8
+
9
+ A workflow that runs indefinitely without resetting accumulates unbounded event history, eventually degrading performance.
10
+
11
+ ```twf
12
+ # BAD: Infinite loop with no history reset
13
+ # workflow EventProcessor(config: Config):
14
+ # for:
15
+ # activity PollEvents(config) -> events
16
+ # activity ProcessBatch(events)
17
+
18
+ # GOOD: Continue-as-new resets history periodically
19
+ workflow EventProcessor(config: Config):
20
+ state:
21
+ condition shutdownRequested
22
+ signal Shutdown():
23
+ set shutdownRequested
24
+ for:
25
+ if (shutdownRequested):
26
+ close complete
27
+ activity PollEvents(config) -> events
28
+ activity ProcessBatch(events)
29
+ close continue_as_new(config)
30
+
31
+ activity PollEvents(config: Config) -> (Events):
32
+ return poll(config)
33
+
34
+ activity ProcessBatch(events: Events):
35
+ process(events)
36
+ ```
37
+
38
+ **Why:** Temporal stores every event in workflow history. Long-running workflows without `close continue_as_new` grow history without bound, causing slow replays and eventual failure. See [long-running.md](../topics/long-running.md).
39
+
40
+ > **Bounded is not automatically safe.** The rule is not "infinite loops need `continue_as_new`" — it's "**loops whose accumulated history is large need `continue_as_new`; a bound alone is not sufficient.**" A loop with an internal bound (e.g. `for` over 40 iterations) still grows history linearly, and if per-iteration history is chunky (a large `LlmCall` result plus N tool calls each iteration) or the bound is high, it can blow the limit before finishing. State the strategy explicitly in the design — "bounded at N, per-iteration history small, no `continue_as_new`" or "resets every K iterations" — rather than leaving it silent.
41
+
42
+ ### Wrapper Workflow
43
+
44
+ A child workflow containing a single activity call adds orchestration overhead with no benefit.
45
+
46
+ ```pseudo
47
+ # BAD: Unnecessary child workflow wrapper
48
+ workflow Parent():
49
+ workflow SendEmailWorkflow(to, body)
50
+
51
+ workflow SendEmailWorkflow(to, body):
52
+ activity SendEmail(to, body)
53
+ close complete
54
+
55
+ # GOOD: Call the activity directly
56
+ workflow Parent():
57
+ activity SendEmail(to, body)
58
+ ```
59
+
60
+ **Why:** Child workflows create separate history, require their own task queue routing, and add latency. Use them only when you need independent retry policies, a separate failure boundary, or multi-step orchestration.
61
+
62
+ ### Monolithic Workflow
63
+
64
+ All business logic in a single workflow with dozens of sequential steps.
65
+
66
+ ```pseudo
67
+ # BAD: One workflow doing everything
68
+ workflow ProcessOrder(order):
69
+ activity Validate(order)
70
+ activity CheckInventory(order)
71
+ activity ReserveInventory(order)
72
+ activity ChargePayment(order)
73
+ activity CreateShipment(order)
74
+ activity NotifyWarehouse(order)
75
+ activity UpdateCRM(order)
76
+ activity SendConfirmation(order)
77
+ activity ScheduleFollowUp(order)
78
+ # ... 20 more steps
79
+
80
+ # GOOD: Decompose into child workflows with clear boundaries
81
+ workflow ProcessOrder(order):
82
+ activity ValidateOrder(order) -> validated
83
+ workflow FulfillOrder(validated) -> fulfillment
84
+ workflow NotifyStakeholders(order, fulfillment)
85
+ close complete(OrderResult{fulfillment})
86
+ ```
87
+
88
+ **Why:** Large workflows have large histories (slow replay), make failure recovery coarse-grained (one failure may require re-running unrelated steps), and are hard to test. Decompose when a group of steps has its own lifecycle, retry needs, or failure boundary.
89
+
90
+ ### Large Payloads in Workflow State
91
+
92
+ Storing large data (files, full database results, images) in workflow variables or signal/update payloads.
93
+
94
+ ```pseudo
95
+ # BAD: Entire dataset in workflow state
96
+ workflow AnalyzeData(datasetId):
97
+ activity FetchDataset(datasetId) -> dataset # 500MB result stored in history
98
+ activity Analyze(dataset) -> results
99
+
100
+ # GOOD: Pass references, not data
101
+ workflow AnalyzeData(datasetId):
102
+ activity FetchAndStore(datasetId) -> dataRef # Returns S3 key, not data
103
+ activity Analyze(dataRef) -> results
104
+ ```
105
+
106
+ **Why:** Every activity input and result is persisted in workflow history. Large payloads bloat history size, slow down replay, and may exceed Temporal's payload size limit. Pass references (IDs, URLs, keys) instead of data.
107
+
108
+ > **Default: defer to the payload codec.** Temporal supports a **payload codec / data converter** that transparently offloads, compresses, or encrypts large payloads with *no change* to workflow/activity signatures — and the claim-check pattern itself is, most of the time, best implemented as a codec server that swaps a large payload for a reference behind the scenes. So the decision is:
109
+ >
110
+ > - **Default — let the codec server handle it** (claim-check included). Note it cleanly and move on: *"large-payload claim-check handled by the codec server."* Do **not** invent a bespoke claim-check store at design time.
111
+ > - **Escalate to an explicit application-level `*Ref`** only when the data outlives the workflow, is shared across services, or needs an ownership/GC story the codec can't own. *Only in that case* does the design owe a one-line note on backing store + lifecycle.
112
+
113
+ ## Primitive Misuse
114
+
115
+ ### Signal for Request-Response
116
+
117
+ Using a signal when the caller needs confirmation or a return value.
118
+
119
+ ```pseudo
120
+ # BAD: Signal has no return value — caller doesn't know if it worked
121
+ signal ApproveOrder(orderId):
122
+ approved = true
123
+
124
+ # GOOD: Update returns a result to the caller
125
+ update ApproveOrder(orderId: string) -> (ApprovalResult):
126
+ activity ValidateApproval(orderId) -> validation
127
+ if (validation.ok):
128
+ approved = true
129
+ return ApprovalResult{accepted: true}
130
+ else:
131
+ return ApprovalResult{accepted: false, reason: validation.error}
132
+ ```
133
+
134
+ **Why:** Signals are fire-and-forget — the sender gets no acknowledgment, no validation, and no result. Use `update` when the caller needs to know the mutation was accepted.
135
+
136
+ ### Query That Modifies State
137
+
138
+ Using a query handler to change workflow state.
139
+
140
+ ```pseudo
141
+ # BAD: Query with side effects
142
+ query GetOrderStatus():
143
+ accessCount = accessCount + 1 # Modifies state!
144
+ return OrderStatus{status, accessCount}
145
+
146
+ # GOOD: Query is a pure read
147
+ query GetOrderStatus():
148
+ return OrderStatus{status}
149
+ ```
150
+
151
+ **Why:** Queries are read-only by contract. They may be called multiple times during replay without the workflow's knowledge. State modifications in queries produce unpredictable behavior and violate Temporal's execution model.
152
+
153
+ ### Update Without Validation
154
+
155
+ Accepting an update without checking whether the mutation is valid.
156
+
157
+ ```pseudo
158
+ # BAD: Blindly applies the update
159
+ update SetShippingAddress(address):
160
+ shippingAddress = address
161
+ return Result{ok: true}
162
+
163
+ # GOOD: Validate before committing
164
+ update SetShippingAddress(address: Address) -> (Result):
165
+ activity ValidateAddress(address) -> validation
166
+ if (validation.valid):
167
+ shippingAddress = address
168
+ return Result{ok: true}
169
+ else:
170
+ return Result{ok: false, error: validation.reason}
171
+ ```
172
+
173
+ **Why:** Updates execute inside the workflow — invalid data corrupts workflow state. Always validate before committing. The caller receives the validation result, so they can react to rejection.
174
+
175
+ ### Detach When You Need the Result
176
+
177
+ Using `detach` on a child workflow or nexus call when the parent needs the outcome.
178
+
179
+ ```pseudo
180
+ # BAD: Detached — parent can't observe success or failure
181
+ detach workflow ProcessPayment(order)
182
+ # ... parent continues, has no idea if payment succeeded
183
+
184
+ # GOOD: Synchronous call or promise when result matters
185
+ workflow ProcessPayment(order) -> paymentResult
186
+ # or: promise p <- workflow ProcessPayment(order) ... await p -> paymentResult
187
+ ```
188
+
189
+ **Why:** `detach` is fire-and-forget — the parent cannot await the result, check for errors, or compensate on failure. Use `detach` only when you genuinely don't care about the outcome (audit logs, analytics, notifications where failure is acceptable).
190
+
191
+ ## Activity Anti-Patterns
192
+
193
+ ### Non-Determinism in Workflows
194
+
195
+ Using non-deterministic operations directly in workflow code.
196
+
197
+ ```pseudo
198
+ # BAD: Current time varies on replay
199
+ # if (current_time() > deadline):
200
+ # cancel()
201
+
202
+ # BAD: Map iteration order varies across replays
203
+ # for (key in map.keys()):
204
+ # activity Process(key)
205
+
206
+ # BAD: Goroutines/threads — execution order not deterministic
207
+ # go func() { activity DoWork() }
208
+
209
+ # GOOD: Use Temporal primitives for time
210
+ # await one:
211
+ # activity DoWork() -> result:
212
+ # close complete(Result{result})
213
+ # timer(deadline):
214
+ # close fail(Result{status: "timeout"})
215
+
216
+ # GOOD: Sort before iterating
217
+ # for (key in sorted(map.keys())):
218
+ # activity Process(key)
219
+
220
+ # GOOD: Use promises for concurrency
221
+ # promise a <- activity DoWorkA()
222
+ # promise b <- activity DoWorkB()
223
+ # await a -> resultA
224
+ # await b -> resultB
225
+ ```
226
+
227
+ **Why:** Temporal replays workflow code to reconstruct state. Any operation that produces different results on replay — time, random numbers, non-deterministic iteration, language-level threading — causes non-determinism errors. See [core-principles.md](./core-principles.md).
228
+
229
+ ### Non-Idempotent Activities
230
+
231
+ Activities that fail or produce incorrect results on retry.
232
+
233
+ ```pseudo
234
+ # BAD: Assumes fresh state — duplicate user on retry
235
+ activity CreateUser(name):
236
+ db.insert(User(name))
237
+
238
+ # GOOD: Create-or-get — idempotent
239
+ activity CreateUser(name):
240
+ existing = db.get_by_name(name)
241
+ if existing: return existing
242
+ return db.insert(User(name))
243
+ ```
244
+
245
+ **Why:** Activities may be retried on network failures, worker crashes, or timeouts. An activity that isn't idempotent (same inputs → same result) will produce duplicate records, double charges, or inconsistent state. See [core-principles.md](./core-principles.md) for idempotency patterns.
246
+
247
+ ### Orchestration in Activities
248
+
249
+ Putting multi-step logic, retry loops, or conditional branching inside an activity.
250
+
251
+ ```pseudo
252
+ # BAD: Multi-step orchestration in activity — partial failure unrecoverable
253
+ activity DeployAll(specs):
254
+ for spec in specs:
255
+ deploy(spec) # If this fails on spec #5 of 10,
256
+ wait_healthy(spec) # specs 1-4 deployed but no rollback
257
+ ```
258
+
259
+ ```twf
260
+ # GOOD: Workflow orchestrates, each step independently retryable
261
+ workflow DeployAll(specs: Specs):
262
+ for (spec in specs.items):
263
+ activity Deploy(spec)
264
+ activity WaitHealthy(spec)
265
+ close complete
266
+
267
+ activity Deploy(spec: Spec):
268
+ deploy(spec)
269
+
270
+ activity WaitHealthy(spec: Spec):
271
+ wait_healthy(spec)
272
+ ```
273
+
274
+ **Why:** Activities run outside Temporal's durable execution model — if an activity fails mid-way through a loop, there's no replay, no history, and no way to resume from the last successful step. Workflows provide exactly this: durable, retryable orchestration with full visibility into progress.
275
+
276
+ ### Activity Sprawl / Wrapping In-Memory Work
277
+
278
+ Wrapping work in an activity when nothing touches an external system — reading data the workflow already holds, in-memory derivation, or accumulation.
279
+
280
+ ```pseudo
281
+ # BAD: activities that touch no external system
282
+ activity ReadCritiqueReady(state) -> ready # field access on held data
283
+ activity ListSubsetPaperIds(papers) -> ids # in-memory filter
284
+ activity AppendObservation(list, obs) -> list # building a collection
285
+
286
+ # GOOD: this is workflow code
287
+ ready = state.critiqueReady
288
+ ids = filter(papers, isSubset)
289
+ observations = append(observations, obs)
290
+ ```
291
+
292
+ **Why:** Each spurious activity is a task-queue round-trip plus a history event for no resilience benefit. Activities are for I/O and side effects; in-memory work is deterministic workflow code. See [core-principles.md](./core-principles.md#activities-are-for-io--not-in-memory-work).
293
+
294
+ ## Deployment Topology
295
+
296
+ ### Nexus for Same-Namespace Calls
297
+
298
+ Using a Nexus operation to call a workflow that lives in the same namespace.
299
+
300
+ ```pseudo
301
+ # BAD: Nexus hop within one namespace — adds an endpoint, a service
302
+ # contract, and latency for no boundary benefit
303
+ nexus InternalEndpoint InternalService.DoStep(args) -> result
304
+
305
+ # GOOD: same namespace — call the child workflow (or activity) directly
306
+ workflow DoStep(args) -> result
307
+ ```
308
+
309
+ **Why:** Nexus exists to cross an **organizational** boundary — different team, security context, deployment lifecycle, or an external service contract. Within a single namespace those boundaries don't exist, so Nexus only adds an endpoint declaration, a typed contract, and a network hop. Coupling between workflows is an argument for *co-location*, not a Nexus boundary. See [namespaces.md](./namespaces.md) and [workflow-boundaries.md](./workflow-boundaries.md#use-nexus-when).
310
+
311
+ ### Deployment Config in Workers Instead of Namespaces
312
+
313
+ Putting task queues, concurrency limits, or other deployment options on `worker` definitions.
314
+
315
+ ```pseudo
316
+ # BAD: worker carries deployment config
317
+ worker orderTypes:
318
+ workflow ProcessOrder
319
+ options:
320
+ task_queue: "orders" # workers are type sets, not deployments
321
+
322
+ # GOOD: worker is a reusable type set; the namespace instantiates it with config
323
+ worker orderTypes:
324
+ workflow ProcessOrder
325
+
326
+ namespace ecommerce:
327
+ worker orderTypes
328
+ options:
329
+ task_queue: "orders"
330
+ ```
331
+
332
+ **Why:** A `worker` is a *reusable type set* (which workflows/activities/services run together) with no deployment config; the `namespace` is what instantiates it with a `task_queue` and options. Mixing the two prevents reusing the same type set across namespaces (staging vs prod) and is rejected by `twf check`. See [task-queues.md](../topics/task-queues.md).
@@ -0,0 +1,88 @@
1
+ # Common Errors
2
+
3
+ This file covers **parser, resolver, and validator diagnostics** emitted by
4
+ `twf check` and `twf parse`. For **design-level anti-patterns** (structural
5
+ mistakes, primitive misuse), see [anti-patterns.md](./anti-patterns.md).
6
+
7
+ Each row lists the symbolic `code` (stable across releases), the human
8
+ message you'll see, the cause, and the fix. The codes are also emitted by
9
+ `twf parse` inside the structured envelope (`diagnostics[].code`); programmatic
10
+ consumers should match on `kind+code` rather than the message.
11
+
12
+ ## Resolve errors (kind: `resolve`)
13
+
14
+ | Code | Message | Cause | Fix |
15
+ |------|---------|-------|-----|
16
+ | `UNDEFINED_ACTIVITY` | `undefined activity: Foo` | Activity `Foo` is called but not defined | Add `activity Foo(...):` definition to the file |
17
+ | `UNDEFINED_WORKFLOW` | `undefined workflow: Foo` | Child workflow `Foo` is called but not defined | Add `workflow Foo(...):` definition to the file |
18
+ | `UNDEFINED_SIGNAL` | `undefined signal: Foo` | `await signal Foo` or `signal Foo:` case but no signal handler declared | Add `signal Foo(...):` declaration inside the workflow, before the body |
19
+ | `UNDEFINED_UPDATE` | `undefined update: Foo` | `await update Foo` or `update Foo:` case but no update handler declared | Add `update Foo(...) -> (Type):` declaration inside the workflow, before the body |
20
+ | `UNDEFINED_CONDITION` | `undefined condition: Foo` | `set Foo`, `unset Foo`, or `await Foo` but no condition declared | Add `condition Foo` inside the workflow's `state:` block |
21
+ | `UNDEFINED_PROMISE_OR_CONDITION` | `undefined promise or condition: Foo` | `await Foo` or `Foo:` case in `await one` but `Foo` is not a promise or condition | Add `promise Foo <- ...` in the workflow body or `condition Foo` in the `state:` block |
22
+ | `DUPLICATE_WORKFLOW` | `duplicate workflow definition: Foo` | Two `workflow Foo` definitions in the same file | Remove or rename the duplicate |
23
+ | `DUPLICATE_ACTIVITY` | `duplicate activity definition: Foo` | Two `activity Foo` definitions in the same file | Remove or rename the duplicate |
24
+ | `DUPLICATE_WORKER` | `duplicate worker definition: Foo` | Two `worker Foo` definitions | Remove or rename the duplicate |
25
+ | `DUPLICATE_NAMESPACE` | `duplicate namespace definition: Foo` | Two `namespace Foo` blocks | Remove or rename the duplicate |
26
+ | `DUPLICATE_NEXUS_SERVICE` | `duplicate nexus service definition: Foo` | Two `nexus service Foo` blocks | Remove or rename the duplicate |
27
+ | `DUPLICATE_ENDPOINT` | `duplicate nexus endpoint name "Foo": defined in namespace A and namespace B` | Same endpoint name in multiple namespaces | Use unique endpoint names |
28
+ | `CONDITION_RESULT_BINDING` | `condition "Foo" cannot have a result binding (-> identifier)` | `await Foo -> result` where `Foo` is a condition | Conditions are boolean — remove the `-> result` binding |
29
+ | `NEXUS_ASYNC_UNDEFINED_WORKFLOW` | `async operation Foo references undefined workflow: Bar` | Async nexus op points at a workflow that doesn't exist | Add the workflow or fix the name |
30
+ | `NEXUS_UNDEFINED_ENDPOINT` | `undefined nexus endpoint: Foo` | Endpoint referenced but not defined anywhere | Add a `nexus endpoint Foo:` in some namespace, or fix the name |
31
+ | `NEXUS_UNDEFINED_SERVICE` | `undefined nexus service: Foo` | Service referenced but not defined | Add a `nexus service Foo:` block or fix the name |
32
+ | `NEXUS_NO_OPERATION` | `nexus service Foo has no operation Bar` | Operation name not in the service | Add the operation or fix the name |
33
+ | `WORKER_UNDEFINED_WORKFLOW` / `WORKER_UNDEFINED_ACTIVITY` / `WORKER_UNDEFINED_NEXUS_SERVICE` | `worker X references undefined ...` | Worker lists a name that doesn't exist | Add the definition or fix the name |
34
+ | `NAMESPACE_UNDEFINED_WORKER` | `namespace X references undefined worker: Y` | Namespace uses unknown worker | Add worker block or fix name |
35
+
36
+ ### Nexus resolution: external (warning) vs. local (error)
37
+
38
+ Nexus references resolve in one of two modes, decided **per category** — services and
39
+ endpoints are independent axes:
40
+
41
+ | Category | Nothing of that category defined in the file set | Any of that category defined |
42
+ |----------|--------------------------------------------------|------------------------------|
43
+ | Service | `NEXUS_UNRESOLVED_SERVICE` — warning, exit 0 ("may be external") | `NEXUS_UNDEFINED_SERVICE` — error, exit 1 |
44
+ | Endpoint | `NEXUS_UNRESOLVED_ENDPOINT` — warning, exit 0 ("may be external") | `NEXUS_UNDEFINED_ENDPOINT` — error, exit 1 |
45
+
46
+ (Endpoints are defined inside `namespace` blocks; services via top-level `nexus service`.)
47
+
48
+ **Gotcha:** defining *one* local service retroactively turns *every other* service reference
49
+ into a hard error — even references to genuinely external services in other namespaces. This is
50
+ a sharp cliff for a partial / per-package file that both *calls* external services and *provides*
51
+ its own. Until an explicit external marker exists, either (a) add a local stub definition for
52
+ each external service you call, or (b) define no nexus services in the file and accept the
53
+ warnings.
54
+
55
+ ## Parse errors (kind: `parse`)
56
+
57
+ All parse failures share the single code `SYNTAX`. The message carries the
58
+ detail; pin programmatic dispatch to `kind=parse, code=SYNTAX` and match on
59
+ the message for now (categorical parse codes are future work).
60
+
61
+ | Message | Cause | Fix |
62
+ |---------|-------|-----|
63
+ | `<keyword> is not allowed in activity body` | Using a temporal primitive (`workflow`, `activity`, `timer`, `signal`, `await`, etc.) inside an activity definition or query handler | Move the temporal primitive to a workflow. Activities run outside the replay-safe workflow context as normal side-effecting code — temporal primitives require deterministic replay and cannot function in activities. |
64
+ | `expected ( after return type ->` | Return type not parenthesized: `-> Result` | Use `-> (Result)` — return types must be wrapped in parentheses |
65
+ | `expected ( after if` / `expected ( after for` | Missing parentheses around condition/iterator | Use `if (expr):` / `for (x in items):` |
66
+ | `unexpected token <tok> at top level` | Statement or keyword that doesn't start a workflow or activity definition | Ensure all top-level items are `workflow`, `activity`, `worker`, `namespace`, or `nexus service` definitions |
67
+ | `unexpected token <tok> in await one case` | Invalid case type inside `await one:` block | Cases must be `signal`, `update`, `timer`, `activity`, `workflow`, an identifier, or `await all` |
68
+ | `expected COLON, got NEWLINE` | A definition is missing its `:` and indented body — a bare declaration like `activity Foo(x) -> (R)` with nothing under it. `activity`/`workflow`/`sync` nexus op definitions always require a body. (Often followed by a cascading `UNDEFINED_*` because the malformed definition didn't register.) | Add `:` and an indented body. For a not-yet-implemented stub, use a placeholder statement (e.g. `return Foo{}` or a single `log(...)`); a definition cannot be body-less. |
69
+
70
+ ## Validation diagnostics (kind: `validate`)
71
+
72
+ | Code | Severity | Cause | Fix |
73
+ |------|----------|-------|-----|
74
+ | `MISSING_TASK_QUEUE` | error | Worker instantiation has no `task_queue` option | Add `options: task_queue: "..."` to the worker instantiation |
75
+ | `MISSING_ENDPOINT_TASK_QUEUE` | error | Nexus endpoint instantiation has no `task_queue` | Add the option to the endpoint instantiation |
76
+ | `EXPLICIT_ROUTING_MISMATCH` | error | An activity/workflow call's explicit `task_queue` doesn't match any worker registering it | Fix the queue name or register the target on a worker for that queue |
77
+ | `IMPLICIT_ROUTING_MISMATCH` | error | An activity/workflow is called without an explicit `task_queue` and no worker on the caller's queue registers it | Add the target to a worker on the same queue, or pass an explicit `task_queue` option |
78
+ | `ENDPOINT_SERVICE_LINKAGE` | error | Endpoint routes to a task queue but no worker on that queue registers the service | Register the service on a worker for the endpoint's queue |
79
+ | `TASK_QUEUE_MISMATCH` | error | Two workers share a queue but register different type sets | Make the type sets identical, or use distinct queues |
80
+ | `TASK_QUEUE_IDENTICAL` | warning | Two workers register identical type sets on the same queue (redundant) | Drop one of the workers |
81
+ | `UNCOVERED_WORKFLOW` / `UNCOVERED_ACTIVITY` / `UNCOVERED_SERVICE` | warning | Definition exists but no instantiated worker registers it | Register on a worker or remove the unused definition |
82
+ | `UNINSTANTIATED_WORKER` | warning | Worker defined but never instantiated in any namespace | Instantiate it in a namespace, or remove the worker |
83
+ | `EMPTY_WORKFLOW` / `EMPTY_ACTIVITY` / `EMPTY_WORKER` / `EMPTY_NAMESPACE` | warning | Block has no body / no registrations / no instantiations | Add content or remove the empty block |
84
+
85
+ The diagnostic shape is the `envelope.Diagnostic` Go struct
86
+ (`tools/lsp/cmd/twf/internal/envelope/model.go`); run any `twf --json`
87
+ subcommand to see it live, or read its TypeScript projection in
88
+ `tools/wire-types`.
@@ -0,0 +1,52 @@
1
+ # Determinism & Idempotency
2
+
3
+ ## Determinism: Workflows Must Replay Identically
4
+
5
+ Temporal replays workflow code to reconstruct state. Different replay results = non-determinism errors. See [Temporal: Deterministic Constraints](https://docs.temporal.io/workflows#deterministic-constraints) for the authoritative reference.
6
+
7
+ | Safe in Workflows | Must Be in Activities |
8
+ |-------------------|----------------------|
9
+ | Logic on activity results | Current time, dates |
10
+ | Deterministic loops/conditionals | Random numbers, UUIDs |
11
+ | Child workflows | HTTP/API calls |
12
+ | Temporal timers | Database operations |
13
+ | Local variables | File I/O |
14
+ | Signal waits | External service calls |
15
+ | Deterministic iteration (arrays, slices) | Map/dictionary iteration (order varies) |
16
+ | Temporal SDK concurrency (promises, await all) | Language-level threads, goroutines, async |
17
+ | Workflow-local state | Mutable global/shared state |
18
+
19
+ **Workflows = pure orchestration. Activities = side effects.**
20
+
21
+ ### Activities Are for I/O — Not In-Memory Work
22
+
23
+ The table above is often *over*-applied into activity sprawl: wrapping things in an activity that were never side effects. **Activities are for I/O and side effects. Do not wrap in an activity:**
24
+
25
+ - **Reads of data the workflow already holds** — field access, lookups into a struct/ref passed in or returned by an earlier activity (`ReadCritiqueReady`, `LookupBundleRef`). The workflow already has the data; reading it is workflow code.
26
+ - **In-memory derivation** — filtering, mapping, computing a value from inputs the workflow holds (`ListSubsetPaperIds`).
27
+ - **Accumulation** — appending to a list or building up state (`AppendObservations`, `AppendTrajectory`). Building a collection is deterministic in-memory work and belongs in the workflow body (expressible directly, including as a raw statement) — there is no need for an `Append*` activity.
28
+
29
+ Each spurious activity is a task-queue round-trip plus a history event for **no resilience benefit**. The litmus test: *does it touch an external system or produce a side effect?* If not, it's workflow code.
30
+
31
+ > **Optimization (away from the default):** batch several small calls into one activity only when they always succeed/fail together and per-call retry isn't meaningful; consider local activities for short deterministic helpers. These are deviations from "one activity per network call" (see [workflow-boundaries.md](./workflow-boundaries.md)), not the starting point.
32
+
33
+ ## Idempotency: Activities May Run Multiple Times
34
+
35
+ Retries happen (network failures, crashes, timeouts). Activities must be **idempotent**: same inputs → same result regardless of execution count.
36
+
37
+ | Pattern | Example |
38
+ |---------|---------|
39
+ | **Create-or-get** — when entity has a natural unique key | Check existence before creating |
40
+ | **Idempotency keys** — when external system supports them | Workflow ID + activity name as operation key |
41
+ | **Upsert** — when database supports atomic upsert | Prefer over insert-then-update |
42
+ | **Deduplication** — last resort when no built-in mechanism | Query before mutating |
43
+
44
+ **Think through retries:** CreateUser → return existing if exists. SendEmail → provider idempotency key. DeployResource → verify state, return success if deployed.
45
+
46
+ ### State the Strategy in the Design
47
+
48
+ Knowing the patterns isn't enough — the *design* must **state** which one each side-effecting activity uses, so idempotency is a load-bearing decision rather than an assumed prose comment. For every activity that isn't idempotent by nature, name its strategy and key derivation, e.g.:
49
+
50
+ > `ChargePayment` — idempotency key = `"{workflow_id}-ChargePayment"`; provider dedupes on it.
51
+
52
+ This is a **skill/design concern, not a `twf check` rule** — Temporal has no call-site `idempotency_key` option, so the parser cannot validate it. The [Design Review](../SKILL.md#design-review) checks that each non-idempotent activity carries this note.
@@ -0,0 +1,59 @@
1
+ # Design Checklist
2
+
3
+ **Validation ≠ review.** *Validation* asks "does it parse and resolve?" (`twf check`). *Review* asks "is it a good Temporal design?" — and no tool answers that. A clean `twf check` clears only the first group below; the [Design Review](#design-review) group is where design quality lives. Don't present on a green tool alone.
4
+
5
+ ## TWF Validation
6
+ - [ ] `twf check` passes (`✓ OK`)
7
+ - [ ] `twf symbols` lists all expected definitions
8
+ - [ ] No undefined references
9
+ - [ ] No SDK-specific code in `.twf`
10
+ → See [common-errors.md](./common-errors.md) for error troubleshooting
11
+
12
+ ## Design Review (fresh-eyes pass — no tool catches these)
13
+ - [ ] **Call-site integrity** — every activity/workflow/nexus definition has a *structured* call site (no orphaned `x = Name(args)` parsing as `raw`)
14
+ - [ ] **Reachability** — every workflow is reachable from a declared entry point; no dead workflows
15
+ - [ ] Design re-checked against **every** anti-pattern in [anti-patterns.md](./anti-patterns.md)
16
+ - [ ] Each non-idempotent activity names its idempotency strategy + key derivation
17
+ - [ ] Concurrent fan-out branches that write shared external state state their isolation/keying assumption
18
+ → See [SKILL.md § Design Review](../SKILL.md#design-review)
19
+
20
+ ## Determinism
21
+ - [ ] All I/O, time, randomness in activities
22
+ - [ ] No external calls in workflow code
23
+ - [ ] Loops have deterministic bounds
24
+ - [ ] Timers use Temporal primitives
25
+ - [ ] No non-deterministic data structure iteration (maps, sets)
26
+ - [ ] Version-specific branching uses proper versioning pattern
27
+ → See [core-principles.md](./core-principles.md) for determinism rules
28
+
29
+ ## Idempotency
30
+ - [ ] Activities handle "already exists" gracefully
31
+ - [ ] Retries produce same end state
32
+ - [ ] No duplicate side effects on replay
33
+ → See [core-principles.md](./core-principles.md) for idempotency patterns
34
+
35
+ ## Failure Handling
36
+ - [ ] Each failure mode identified
37
+ - [ ] Recovery strategy defined (retry, compensate, fail)
38
+ - [ ] Partial success handled
39
+ - [ ] Timeouts configured
40
+ → See [anti-patterns.md](./anti-patterns.md) for common failure handling mistakes
41
+
42
+ ## Runtime, Cost & Lifecycle
43
+ - [ ] Loops with large accumulated history reach `close continue_as_new` (bound alone is not enough); strategy stated
44
+ - [ ] Large payloads: either deferred to the data converter/codec, or (if an explicit claim-check `*Ref`) a one-line store + lifecycle note
45
+ → See [anti-patterns.md](./anti-patterns.md#large-payloads-in-workflow-state) and [long-running.md](../topics/long-running.md)
46
+
47
+ ## Decomposition
48
+ - [ ] Each workflow has single clear purpose
49
+ - [ ] Child workflow vs activity choice justified
50
+ - [ ] Workflow names describe outcomes, not steps
51
+ → See [workflow-boundaries.md](./workflow-boundaries.md) for boundary decisions
52
+
53
+ ## Deployment Topology (design review — `twf check` validates syntax)
54
+ - [ ] Worker groupings reflect actual deployment needs (not just "one worker for everything")
55
+ - [ ] Task queue separation matches scaling and isolation requirements
56
+ - [ ] Namespace count justified by org / security / lifecycle / external-contract boundaries (default: one)
57
+ - [ ] Cross-namespace calls have nexus endpoints
58
+ - [ ] `twf check` passes topology validation
59
+ → See [task-queues.md](../topics/task-queues.md) for task queue design and [namespaces.md](./namespaces.md) for namespace count
@@ -0,0 +1,84 @@
1
+ # Namespaces: How Many?
2
+
3
+ How many namespaces should a design use? This is a cross-cutting deployment decision — it interacts with workers, task queues, and Nexus — so it lives here as a boundary call alongside [workflow-boundaries.md](./workflow-boundaries.md), not as a construct deep-dive. The load-bearing rule:
4
+
5
+ > **Namespaces are organizational, not architectural.** They are an operational/ownership boundary, not a decomposition tool. The default is **one**; adding more requires justification.
6
+
7
+ Without this rule, designs drift toward "one namespace per layer → one per worker," which is almost always an overuse. Agent/tool scoping is solved by worker registration; runtime heterogeneity by task queues; layer separation by workflow boundaries. **None of those justify a namespace.**
8
+
9
+ ---
10
+
11
+ ## Decision Ladder
12
+
13
+ ### Default: one namespace
14
+
15
+ Start here. A single namespace holds all your workers, workflows, and activities. Tightly-coupled workflows belong **together** — coupling is an argument for co-location, not separation.
16
+
17
+ ### Add a namespace only when one of these demands it
18
+
19
+ | Reason | Why it's a namespace boundary |
20
+ |--------|-------------------------------|
21
+ | **Distinct team owns the workflows** | Separate ownership, access control, on-call |
22
+ | **Different security / compliance context** | PCI vs non-PCI, tenant isolation at the org level |
23
+ | **Independent deployment lifecycle** | Separate release cadence and blast radius |
24
+ | **External service contract across an org boundary** | The Nexus case — a typed contract between services |
25
+
26
+ ### Explicitly NOT reasons to add a namespace
27
+
28
+ | Drift | What actually solves it |
29
+ |-------|-------------------------|
30
+ | Different worker / runtime (GPU, licensed software) | **Task queues** ([task-queues.md](../topics/task-queues.md)) |
31
+ | Agent or tool scoping | **Worker registration** (which types run together) |
32
+ | Layer separation (inner vs outer logic) | **Workflow boundaries** (child workflows / activities) |
33
+ | "One per worker" by default | Nothing — co-locate them |
34
+ | "It feels cleaner" | Nothing — resist it |
35
+
36
+ ---
37
+
38
+ ## Worked Judgment: Two-Layer Agent System
39
+
40
+ Consider an inner agent (executes tools) and an outer agent (plans, calls the inner agent), each with its own tools.
41
+
42
+ - **Tempting (wrong) start:** a namespace per worker — one for the planner, one for each tool runner — arriving at 5-6 namespaces.
43
+ - **Why it's wrong:** inner-agent vs outer-agent *tool scoping* is **worker registration** (register each agent's tools on its own worker), not a namespace boundary. The layers are tightly coupled, which argues for co-location.
44
+ - **The legitimate split:** the only real boundary is the **org/service contract** between the two layers — if the inner agent is genuinely an independent service with its own contract, that justifies **two** namespaces connected by **Nexus**. Not more.
45
+
46
+ The mechanism is **worker registration for scoping, namespaces only for the service boundary**:
47
+
48
+ ```twf
49
+ # Tool scoping is worker registration, NOT a namespace:
50
+ worker outerAgentWorker:
51
+ workflow OuterAgent
52
+ activity PlanSteps
53
+ activity SummarizeOutcome
54
+
55
+ worker innerAgentWorker:
56
+ workflow InnerAgent
57
+ activity SearchTool
58
+ activity CalcTool
59
+ nexus service InnerAgentService
60
+
61
+ # Exactly two namespaces: one per org/service-contract boundary.
62
+ namespace outerAgent:
63
+ worker outerAgentWorker
64
+ options:
65
+ task_queue: "outer-agent"
66
+
67
+ namespace innerAgent:
68
+ worker innerAgentWorker
69
+ options:
70
+ task_queue: "inner-agent"
71
+ nexus endpoint InnerAgentEndpoint
72
+ options:
73
+ task_queue: "inner-agent"
74
+ ```
75
+
76
+ The final two-namespaces-with-Nexus topology can be a fine outcome; the mistake is *starting* at one-per-worker and being talked back down. Start at one, and require a reason from the ladder above to add each additional namespace.
77
+
78
+ ---
79
+
80
+ ## Related
81
+
82
+ - [task-queues.md](../topics/task-queues.md) — different runtimes use task queues, not namespaces.
83
+ - [nexus.md](../topics/nexus.md) — the cross-namespace contract; the one mechanism that legitimately spans namespaces.
84
+ - [workflow-boundaries.md](./workflow-boundaries.md) — child workflow vs activity vs nexus.