@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,64 @@
|
|
|
1
|
+
# update handler
|
|
2
|
+
|
|
3
|
+
## DSL
|
|
4
|
+
|
|
5
|
+
```twf
|
|
6
|
+
update ChangePlan(newPlan: string) -> (ChangeResult):
|
|
7
|
+
activity ValidatePlan(newPlan) -> validation
|
|
8
|
+
if (validation.valid):
|
|
9
|
+
plan = newPlan
|
|
10
|
+
return ChangeResult{success: true, plan: plan}
|
|
11
|
+
else:
|
|
12
|
+
return ChangeResult{success: false, error: validation.reason}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Go
|
|
16
|
+
|
|
17
|
+
```go
|
|
18
|
+
err := workflow.SetUpdateHandlerWithOptions(ctx, "ChangePlan",
|
|
19
|
+
func(ctx workflow.Context, newPlan string) (ChangeResult, error) {
|
|
20
|
+
var validation Validation
|
|
21
|
+
err := workflow.ExecuteActivity(ctx, ValidatePlan, newPlan).Get(ctx, &validation)
|
|
22
|
+
if err != nil {
|
|
23
|
+
return ChangeResult{}, err
|
|
24
|
+
}
|
|
25
|
+
if validation.Valid {
|
|
26
|
+
plan = newPlan
|
|
27
|
+
return ChangeResult{Success: true, Plan: plan}, nil
|
|
28
|
+
}
|
|
29
|
+
return ChangeResult{Success: false, Error: validation.Reason}, nil
|
|
30
|
+
},
|
|
31
|
+
workflow.UpdateHandlerOptions{},
|
|
32
|
+
)
|
|
33
|
+
if err != nil {
|
|
34
|
+
return Result{}, err
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Notes
|
|
39
|
+
|
|
40
|
+
- Update handlers receive `workflow.Context` as first param (unlike queries) — they can call activities and use temporal primitives
|
|
41
|
+
- Update handlers can modify workflow state (unlike queries)
|
|
42
|
+
- The caller blocks until the handler returns
|
|
43
|
+
- Update handlers cannot call `close` — only the main workflow body can terminate the workflow
|
|
44
|
+
- Register updates at the very start of the workflow function, before any blocking calls
|
|
45
|
+
|
|
46
|
+
## Validators
|
|
47
|
+
|
|
48
|
+
- Set via `workflow.UpdateHandlerOptions{Validator: func(...) error{...}}` in `SetUpdateHandlerWithOptions`
|
|
49
|
+
- **Validators reject updates before History write.** If the validator returns an error, no events are recorded — the update "disappears." The caller receives an "Update failed" error
|
|
50
|
+
- **Handler errors are post-acceptance.** Once the validator passes (or is absent), `WorkflowExecutionUpdateAccepted` is written. If the handler then returns an error, `WorkflowExecutionUpdateCompleted` records the failure — but the acceptance event persists
|
|
51
|
+
- The validator function has the same parameter types as the handler but returns only `error`. It may optionally include or omit `workflow.Context` as the first parameter
|
|
52
|
+
- **Validators must not mutate workflow state** — no variable assignment, no scheduling activities, no side-effects. They may read workflow state
|
|
53
|
+
- **Validators must not block** — same restriction as query handlers
|
|
54
|
+
- A panic in a validator is treated as a rejection (equivalent to returning an error)
|
|
55
|
+
|
|
56
|
+
## Pitfalls
|
|
57
|
+
|
|
58
|
+
- **Missing validator.** The example above performs validation via an activity inside the handler — this is post-acceptance validation. To reject invalid updates without writing to History, add a validator
|
|
59
|
+
- **Handler lifetime vs workflow completion.** Default `HandlerUnfinishedPolicy` is `WarnAndAbandon` — if the workflow completes or calls Continue-As-New while an update handler is still running, the handler is abandoned and the caller receives `ServiceError` "workflow execution already completed"
|
|
60
|
+
- To avoid abandoned handlers, wait before exiting:
|
|
61
|
+
```go
|
|
62
|
+
err = workflow.Await(ctx, func() bool { return workflow.AllHandlersFinished(ctx) })
|
|
63
|
+
```
|
|
64
|
+
- **Continue-As-New during handler execution.** The caller gets "workflow execution already completed." The update is lost. Drain handlers before CAN
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# worker
|
|
2
|
+
|
|
3
|
+
## DSL
|
|
4
|
+
|
|
5
|
+
```twf
|
|
6
|
+
worker orderTypes:
|
|
7
|
+
workflow ProcessOrder
|
|
8
|
+
activity ValidateOrder
|
|
9
|
+
activity ChargePayment
|
|
10
|
+
|
|
11
|
+
namespace default:
|
|
12
|
+
worker orderTypes
|
|
13
|
+
options:
|
|
14
|
+
task_queue: "orders"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Go
|
|
18
|
+
|
|
19
|
+
```go
|
|
20
|
+
import (
|
|
21
|
+
"log"
|
|
22
|
+
|
|
23
|
+
"go.temporal.io/sdk/client"
|
|
24
|
+
"go.temporal.io/sdk/worker"
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
func main() {
|
|
28
|
+
c, err := client.Dial(client.Options{})
|
|
29
|
+
if err != nil {
|
|
30
|
+
log.Fatalln("Unable to create client", err)
|
|
31
|
+
}
|
|
32
|
+
defer c.Close()
|
|
33
|
+
|
|
34
|
+
w := worker.New(c, "orders", worker.Options{})
|
|
35
|
+
|
|
36
|
+
w.RegisterWorkflow(ProcessOrder)
|
|
37
|
+
w.RegisterActivity(&Activities{/* dependencies */})
|
|
38
|
+
|
|
39
|
+
err = w.Run(worker.InterruptCh())
|
|
40
|
+
if err != nil {
|
|
41
|
+
log.Fatalln("Unable to start worker", err)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Notes
|
|
47
|
+
|
|
48
|
+
- `worker.New(client, taskQueue, options)` — task queue comes from namespace `options: task_queue`
|
|
49
|
+
- `RegisterWorkflow(func)` — one call per workflow in the worker's type set
|
|
50
|
+
- `RegisterActivity(struct)` — register the activity struct (all exported methods become activities) or individual functions
|
|
51
|
+
- `worker.InterruptCh()` for graceful shutdown on SIGINT/SIGTERM
|
|
52
|
+
- Multiple `worker` blocks in the DSL with different task queues → multiple `worker.New` calls in the same `main()`
|
|
53
|
+
- For nexus services on the same worker, see [nexus-service-def.md](./nexus-service-def.md)
|
|
54
|
+
|
|
55
|
+
## When to use: struct vs function registration
|
|
56
|
+
|
|
57
|
+
- **Struct registration** (`w.RegisterActivity(&Activities{...})`): standard pattern. All exported methods on the struct become activities. Use when activities share dependencies (DB connections, API clients) injected via the struct
|
|
58
|
+
- **Individual function registration** (`w.RegisterActivity(SomeFunc)`): use when activities span multiple structs with different dependency sets, or for standalone functions with no dependencies
|
|
59
|
+
- `RegisterActivityWithOptions` with `Name` sets a prefix for struct methods or overrides the name for individual functions
|
|
60
|
+
- Registration panics if two activities share the same type name. Use `DisableAlreadyRegisteredCheck: true` in tests only
|
|
61
|
+
- If a struct has exported methods that don't match the activity signature `(context.Context, ...) (..., error)`, registration panics. Set `SkipInvalidStructFunctions: true` to skip them
|
|
62
|
+
|
|
63
|
+
### Nil-pointer method binding
|
|
64
|
+
|
|
65
|
+
Workflow bodies reference an activity by **method value on a nil struct pointer** so the call is type-checked and the name is derived from the method — no string literal:
|
|
66
|
+
|
|
67
|
+
```go
|
|
68
|
+
var a *Activities // nil; never dereferenced — only its method value is taken
|
|
69
|
+
workflow.ExecuteActivity(ctx, a.ChargePayment, order).Get(ctx, &receipt)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
The worker must still register a **real, fully-constructed** instance (`w.RegisterActivity(&Activities{db: db})`). The nil pointer in the workflow only names the activity; the registered instance is what actually runs. The two must name the same type.
|
|
73
|
+
|
|
74
|
+
## Dependency injection into activity structs
|
|
75
|
+
|
|
76
|
+
Activities reach external systems through dependencies held on the struct. Construct the struct with its dependencies at startup and register it:
|
|
77
|
+
|
|
78
|
+
```go
|
|
79
|
+
acts := &Activities{db: db, payments: paymentsClient}
|
|
80
|
+
w.RegisterActivity(acts)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
In larger apps this is wired with `fx` (an `fx.go` per package provides the client and the activities struct, and a registration function calls `w.RegisterActivity` / the generated helper). Keep construction at the composition root — workflows and activity bodies never build their own dependencies.
|
|
84
|
+
|
|
85
|
+
## Proto-driven registration
|
|
86
|
+
|
|
87
|
+
When the project is [proto-driven](./proto-driven.md), the generator emits registration helpers — use them instead of hand-registering each type:
|
|
88
|
+
|
|
89
|
+
```go
|
|
90
|
+
pb.RegisterMyServiceActivities(w, acts) // registers every activity on the service at once
|
|
91
|
+
pb.RegisterMyServiceWorkflows(w, wfs) // if the service declares workflows
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
A missing helper call is the proto-driven form of an unregistered type: the worker starts fine and fails the task at runtime.
|
|
95
|
+
|
|
96
|
+
## Nexus service registration
|
|
97
|
+
|
|
98
|
+
Register a Nexus service on the **handler** worker (the target namespace's worker), alongside its handler workflows:
|
|
99
|
+
|
|
100
|
+
```go
|
|
101
|
+
service := nexus.NewService(BillingServiceName)
|
|
102
|
+
_ = service.Register(ChargePaymentOperation)
|
|
103
|
+
w.RegisterNexusService(service)
|
|
104
|
+
w.RegisterWorkflow(BillingChargeWorkflow) // handler workflows must also be registered
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Creating the Nexus **endpoint** that routes to this service is out-of-band infrastructure (`tcld` / Terraform), not worker code — that belongs to a future `author-infra` skill. See [nexus-service-def.md](./nexus-service-def.md) for the handler/operation patterns.
|
|
108
|
+
|
|
109
|
+
## Graceful shutdown
|
|
110
|
+
|
|
111
|
+
`worker.InterruptCh()` stops the worker cleanly on SIGINT/SIGTERM, draining in-flight tasks:
|
|
112
|
+
|
|
113
|
+
```go
|
|
114
|
+
if err := w.Run(worker.InterruptCh()); err != nil {
|
|
115
|
+
log.Fatalln("worker stopped", err)
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Close the Temporal client (`defer c.Close()`) and any injected dependencies (DB pools, external clients) after `Run` returns.
|
|
120
|
+
|
|
121
|
+
## Registration coverage & the TWF↔Go bridge
|
|
122
|
+
|
|
123
|
+
**Coverage reality:**
|
|
124
|
+
|
|
125
|
+
- An **unregistered type fails the task, not the workflow.** The task returns to the queue for another worker; the workflow itself does not fail, but latency and wasted resources accumulate. This silent degradation is why coverage matters.
|
|
126
|
+
- **All workers on the same task queue must register the identical Workflow Type and Activity Type set.** A partial set on one worker means tasks intermittently land on a worker that can't run them.
|
|
127
|
+
- Multiple DSL `worker` blocks with different type sets must use **different task queues**.
|
|
128
|
+
|
|
129
|
+
**The TWF↔Go bridge:** the design-time topology maps directly onto worker wiring —
|
|
130
|
+
|
|
131
|
+
| TWF | Go |
|
|
132
|
+
|-----|-----|
|
|
133
|
+
| `worker Name:` (its workflow/activity members) | the registered type set on one `worker.New` |
|
|
134
|
+
| `namespace` + `task_queue` option | `worker.New(client, taskQueue, ...)` |
|
|
135
|
+
|
|
136
|
+
The resolver previews coverage gaps **at design time**: `UNCOVERED_WORKFLOW` / `UNCOVERED_ACTIVITY` / `UNCOVERED_SERVICE` flag a type no worker covers, and `IMPLICIT_ROUTING_MISMATCH` flags a routing mismatch. Treat a clean resolve as the design-time analog of full registration. These same codes are also a **reverse-reading signal**: when recovering a `.twf` from existing Go, the registered set on each `worker.New` is what populates each `worker` block.
|
|
137
|
+
|
|
138
|
+
## Worker options → `worker.Options`
|
|
139
|
+
|
|
140
|
+
Worker tuning now lives in the namespace `options:` block — the SDK-union worker-options set (concurrency caps, rate limiters, sticky cache, versioning). Map each key onto a `worker.Options` field rather than keeping it out of the `.twf`:
|
|
141
|
+
|
|
142
|
+
| TWF worker option | `worker.Options` field | Go type |
|
|
143
|
+
|-------------------|------------------------|---------|
|
|
144
|
+
| `task_queue` | 2nd arg to `worker.New` (not a field) | string |
|
|
145
|
+
| `max_concurrent_activity_executions` | `MaxConcurrentActivityExecutionSize` | int |
|
|
146
|
+
| `max_concurrent_workflow_task_executions` | `MaxConcurrentWorkflowTaskExecutionSize` | int |
|
|
147
|
+
| `max_concurrent_local_activity_executions` | `MaxConcurrentLocalActivityExecutionSize` | int |
|
|
148
|
+
| `max_concurrent_nexus_task_executions` | `MaxConcurrentNexusTaskExecutionSize` | int |
|
|
149
|
+
| `max_concurrent_workflow_task_pollers` | `MaxConcurrentWorkflowTaskPollers` | int |
|
|
150
|
+
| `max_concurrent_activity_task_pollers` | `MaxConcurrentActivityTaskPollers` | int |
|
|
151
|
+
| `max_concurrent_nexus_task_pollers` | `MaxConcurrentNexusTaskPollers` | int |
|
|
152
|
+
| `worker_activity_rate_limit` | `WorkerActivitiesPerSecond` | float64 |
|
|
153
|
+
| `task_queue_activity_rate_limit` | `TaskQueueActivitiesPerSecond` | float64 |
|
|
154
|
+
| `worker_local_activity_rate_limit` | `WorkerLocalActivitiesPerSecond` | float64 |
|
|
155
|
+
| `sticky_schedule_to_start_timeout` | `StickyScheduleToStartTimeout` | time.Duration |
|
|
156
|
+
| `heartbeat_throttle_interval` | `MaxHeartbeatThrottleInterval` | time.Duration |
|
|
157
|
+
| `worker_identity` | `Identity` | string |
|
|
158
|
+
| `worker_shutdown_timeout` | `WorkerStopTimeout` | time.Duration |
|
|
159
|
+
| `local_activity_only_mode` | `LocalActivityWorkerOnly` | bool |
|
|
160
|
+
| `enable_sessions` | `EnableSessionWorker` | bool |
|
|
161
|
+
| `max_concurrent_session_executions` | `MaxConcurrentSessionExecutionSize` | int |
|
|
162
|
+
| `max_cached_workflows` | **not** a `worker.Options` field — process-global via `worker.SetStickyWorkflowCacheSize(n)` | int |
|
|
163
|
+
| `versioning` | not 1:1 — see below | enum |
|
|
164
|
+
|
|
165
|
+
Example — a namespace worker carrying a couple of options:
|
|
166
|
+
|
|
167
|
+
```twf
|
|
168
|
+
namespace orders:
|
|
169
|
+
worker orderTypes
|
|
170
|
+
options:
|
|
171
|
+
task_queue: "orders"
|
|
172
|
+
max_concurrent_activity_executions: 50
|
|
173
|
+
enable_sessions: true
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
maps to:
|
|
177
|
+
|
|
178
|
+
```go
|
|
179
|
+
w := worker.New(c, "orders", worker.Options{
|
|
180
|
+
MaxConcurrentActivityExecutionSize: 50,
|
|
181
|
+
EnableSessionWorker: true,
|
|
182
|
+
})
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
> **Permissive-union caveat:** the DSL worker-options set is the SDK *union* accepted permissively, so a `.twf` may carry a key the Go SDK has no field for (a per-language one-off). When a key has no `worker.Options` field, **drop it — do not invent an API.** The richer versioning model (ramping, per-namespace-vs-per-worker placement) stays deferred in `internal/changes/dsl/BACKLOG.md`.
|
|
186
|
+
|
|
187
|
+
### `versioning` (not 1:1)
|
|
188
|
+
|
|
189
|
+
The DSL `versioning` enum carries *strategy*, not identifiers. Concrete Build ID / deployment name / version are **deploy-time inputs from config/env** — never fabricate them into generated code.
|
|
190
|
+
|
|
191
|
+
- `none` → no versioning fields (default unversioned worker).
|
|
192
|
+
- `build_id` → **legacy** path (the SDK marks these **Deprecated** in favor of `DeploymentOptions`):
|
|
193
|
+
|
|
194
|
+
```go
|
|
195
|
+
worker.Options{
|
|
196
|
+
BuildID: buildID, // from env/config, not the .twf
|
|
197
|
+
UseBuildIDForVersioning: true,
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
- `deployment` → **current** path:
|
|
202
|
+
|
|
203
|
+
```go
|
|
204
|
+
worker.Options{
|
|
205
|
+
DeploymentOptions: worker.DeploymentOptions{
|
|
206
|
+
UseVersioning: true,
|
|
207
|
+
Version: worker.WorkerDeploymentVersion{
|
|
208
|
+
DeploymentName: deploymentName, // from env/config
|
|
209
|
+
BuildId: buildID, // from env/config
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
> **Pitfall — mutual exclusion:** worker versioning (`UseBuildIDForVersioning` or `DeploymentOptions.UseVersioning`) **cannot** be enabled together with `EnableSessionWorker`. A `.twf` carrying both `versioning: build_id|deployment` and `enable_sessions: true` cannot map to one `worker.Options` — surface it as a conflict; do not silently pick one.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# workflow call (child)
|
|
2
|
+
|
|
3
|
+
## DSL
|
|
4
|
+
|
|
5
|
+
```twf
|
|
6
|
+
workflow ShipOrder(order) -> shipResult
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Go
|
|
10
|
+
|
|
11
|
+
```go
|
|
12
|
+
var shipResult ShipResult
|
|
13
|
+
err := workflow.ExecuteChildWorkflow(ctx, ShipOrder, order).Get(ctx, &shipResult)
|
|
14
|
+
if err != nil {
|
|
15
|
+
return Result{}, err
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Notes
|
|
20
|
+
|
|
21
|
+
- The child workflow function is passed by reference — `workflow.ExecuteChildWorkflow(ctx, FuncName, args...)`
|
|
22
|
+
- `ctx` must carry child workflow options; see [options.md](./options.md) for setting `ChildWorkflowOptions`
|
|
23
|
+
- For fire-and-forget, see [detach.md](./detach.md)
|
|
24
|
+
- For cross-namespace, see [nexus.md](./nexus.md)
|
|
25
|
+
|
|
26
|
+
## When to use
|
|
27
|
+
|
|
28
|
+
- **Use a child workflow when:** the sub-process needs its own event history (parent history has a 51,200 event / 50 MB limit), independent retry/timeout policies, a different task queue, separate cancellation scope (via `ParentClosePolicy`), or represents a composite operation orchestrating multiple steps
|
|
29
|
+
- **Use an activity when:** the operation is a single action on an external system (API call, DB write, message send). "When in doubt, use an Activity" — Temporal's official recommendation
|
|
30
|
+
- Activities add events to the parent's history; child workflows get their own independent history
|
|
31
|
+
- Child workflows cannot share state with the parent — communication is through signals only
|
|
32
|
+
- Do NOT use child workflows solely for code organization — use Go packages and functions instead
|
|
33
|
+
|
|
34
|
+
## Pitfalls
|
|
35
|
+
|
|
36
|
+
- Default `ParentClosePolicy` is `TERMINATE` (child is killed immediately) — if the parent completes, fails, or times out, the child is forcefully terminated. Set `REQUEST_CANCEL` (child receives a cancellation request and can handle it gracefully) or `ABANDON` (child continues running independently) explicitly when the child should outlive the parent. See [options.md](./options.md) for the DSL-to-Go mapping
|
|
37
|
+
- If the parent completes before the `ChildWorkflowExecutionStarted` event is recorded, the child may never spawn. Always call `childFuture.GetChildWorkflowExecution().Get(ctx, nil)` to confirm the child started — see [detach.md](./detach.md)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# workflow definition
|
|
2
|
+
|
|
3
|
+
## DSL
|
|
4
|
+
|
|
5
|
+
```twf
|
|
6
|
+
workflow ProcessOrder(order: Order) -> (Result):
|
|
7
|
+
# body
|
|
8
|
+
close complete(Result{status: "done"})
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Go
|
|
12
|
+
|
|
13
|
+
```go
|
|
14
|
+
import "go.temporal.io/sdk/workflow"
|
|
15
|
+
|
|
16
|
+
func ProcessOrder(ctx workflow.Context, order Order) (Result, error) {
|
|
17
|
+
// body
|
|
18
|
+
return Result{Status: "done"}, nil
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Notes
|
|
23
|
+
|
|
24
|
+
- Every workflow returns `error` as the last return value, even if the DSL has no return type (signature becomes `func Name(ctx workflow.Context, params...) error`)
|
|
25
|
+
- `workflow.Context` is always the first parameter
|
|
26
|
+
- Multiple return types `-> (A, B)` → `func Name(ctx workflow.Context, ...) (A, B, error)`
|
|
27
|
+
|
|
28
|
+
## Determinism constraints
|
|
29
|
+
|
|
30
|
+
Workflow functions must be deterministic — every replay must produce the same commands in the same order. Violating determinism causes a non-deterministic error: the Workflow Task fails and retries indefinitely until the code is fixed.
|
|
31
|
+
|
|
32
|
+
**Forbidden in workflow code:**
|
|
33
|
+
- `time.Now()` → use `workflow.Now(ctx)`
|
|
34
|
+
- `time.Sleep()` → use `workflow.Sleep(ctx, duration)`
|
|
35
|
+
- `math/rand`, `crypto/rand` → use `workflow.SideEffect()` or compute in an activity
|
|
36
|
+
- HTTP clients, network calls, file I/O, database access → move to activities
|
|
37
|
+
- `go` keyword → use `workflow.Go(ctx, func(gCtx workflow.Context) { ... })`
|
|
38
|
+
- Native `chan`, `select` → use `workflow.Channel`, `workflow.Selector`
|
|
39
|
+
- `range` over `map` → sort keys first, then iterate
|
|
40
|
+
- Global mutable state → use workflow-local variables or activity results
|
|
41
|
+
- Standard loggers → use `workflow.GetLogger(ctx)`
|
|
42
|
+
|
|
43
|
+
**Safe changes** (do not break determinism): changing activity/child workflow input values, changing timer durations (except to zero), adding calls that don't produce commands (`workflow.GetInfo()`, `workflow.GetLogger()`).
|
|
44
|
+
|
|
45
|
+
Use `workflow.GetVersion()` or `workflow.Patched()` to safely modify running workflow definitions.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Skill: Temporal Architect — Infra Authoring
|
|
2
|
+
|
|
3
|
+
**Goal:** Provision the control-plane resources a validated TWF design depends on — namespaces, Nexus endpoints, and search attributes — as infrastructure-as-code.
|
|
4
|
+
|
|
5
|
+
**Primary focus:** Topology fidelity. The third implementer of a design, parallel to `author-go`: `author-go` writes the worker code, `author-infra` provisions the resources that code registers against and runs on. Closes the Nexus endpoint registration `author-go` deliberately punts as out-of-band infra.
|
|
6
|
+
|
|
7
|
+
**Scope:**
|
|
8
|
+
- Produces: applied IaC — Terraform (`temporalio/temporalcloud`) or `tcld` / `temporal operator` CLI runbooks
|
|
9
|
+
- Consumes: a validated `.twf` file's deployment topology (`namespace`, `nexus endpoint`, worker task queues)
|
|
10
|
+
- Does not: generate workflow/activity code, make design decisions, or model worker runtime tuning
|
|
11
|
+
|
|
12
|
+
**Authoritative references:**
|
|
13
|
+
- Temporal Cloud Terraform provider (`temporalio/temporalcloud`) and `tcld` / `temporal operator` CLI docs — current resource and command surface
|
|
14
|
+
- `tools/spec/sections/` — source of truth for the `.twf` topology constructs being provisioned
|
|
15
|
+
|
|
16
|
+
**Entry point:** `SKILL.md` → Orient (detect Cloud+Terraform vs self-hosted CLI) → the matching `reference/` file
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: temporal-architect-author-infra
|
|
3
|
+
description: Provision the control-plane resources a .twf design needs — namespaces, Nexus endpoints, search attributes — via the Temporal Cloud Terraform provider or self-hosted tcld / temporal operator CLI. Use when deploying Temporal infrastructure for a workflow design, not worker code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Temporal Architect: Infrastructure Authoring
|
|
7
|
+
|
|
8
|
+
Provision the **out-of-band control-plane resources** a `.twf` design depends on, using infrastructure-as-code. The primary goal is real, applied infrastructure — namespaces, Nexus endpoints, and search attributes — that the worker code (`author-go`) can register against and run on. Always produce IaC artifacts (`.tf` files or CLI runbooks) as deliverables.
|
|
9
|
+
|
|
10
|
+
`author-infra` is the **third implementer** of a design, parallel to `author-go`:
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
design (topology) ──► author-go (worker code: workflows, activities, registration)
|
|
14
|
+
└──► author-infra (provisioning: namespaces, endpoints, search attributes)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The split is deliberate: `author-go` punts Nexus **endpoint** registration ("out-of-band → infra skill") because it is a Terraform/CLI resource, not worker code. This skill closes that gap. Worker code and the resources it runs on are two different artifacts with two different lifecycles.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Core Principles
|
|
22
|
+
|
|
23
|
+
**Provision only the topology, not the code.** This skill reads the `.twf` deployment topology — `namespace`, `nexus endpoint` (with its `worker_target`), and worker task queues — and provisions the control-plane resources for it. It never generates workflow or activity code; that is `author-go`'s job.
|
|
24
|
+
|
|
25
|
+
**The `.twf` is the contract.** Resources derive from declared topology, not invented. If a resource needs an input the `.twf` does not model (see [Not-Yet-Modeled `.twf` Intent](#not-yet-modeled-twf-intent)), ask the user — do not guess at access policies or attribute schemas.
|
|
26
|
+
|
|
27
|
+
**IaC discipline: one owner per resource.** Once a resource is Terraform-managed, manage it *only* through Terraform. Mixing console/CLI edits with Terraform produces drift that the next `terraform plan` will try to revert. For resources that already exist, `import` them into state before managing — never let Terraform try to create a duplicate.
|
|
28
|
+
|
|
29
|
+
**User as decision-maker.** The skill owns the mechanical mapping (topology → resource blocks); the user owns consequential choices — deployment target, region selection, retention, auth model, and which caller namespaces an endpoint trusts. Surface these as specific options with tradeoffs, not open-ended questions.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Process
|
|
34
|
+
|
|
35
|
+
### Orient
|
|
36
|
+
|
|
37
|
+
Before provisioning, resolve **where the infrastructure lands** along two independent axes — the same model as `author-go`.
|
|
38
|
+
|
|
39
|
+
| Axis | Existing infra | Greenfield |
|
|
40
|
+
|------|----------------|------------|
|
|
41
|
+
| **1. Greenfield vs. existing** | *Detect* — signals below; if any fire, infra already exists | *Ask* the user |
|
|
42
|
+
| **2. Deployment target** | *Detect from the repo* and conform to it | *Ask*; no default — Cloud vs self-hosted is a real decision |
|
|
43
|
+
|
|
44
|
+
**Cheap detection signals** (check these first — no subagent needed):
|
|
45
|
+
|
|
46
|
+
- `*.tf` with `temporalio/temporalcloud` in `required_providers` → **Temporal Cloud + Terraform**
|
|
47
|
+
- existing `temporalcloud_namespace` / `temporalcloud_nexus_endpoint` resource blocks → **Temporal Cloud + Terraform** (and candidates for `import`)
|
|
48
|
+
- `tcld` invocations in scripts/CI, or a self-hosted `temporal server` / Helm chart → **self-hosted + `tcld` / `temporal operator`**
|
|
49
|
+
- no infra signals at all → greenfield; *ask* the user which target
|
|
50
|
+
|
|
51
|
+
**Target → reference routing:**
|
|
52
|
+
|
|
53
|
+
| Target | Load |
|
|
54
|
+
|--------|------|
|
|
55
|
+
| Temporal Cloud, declarative (Terraform) | [terraform.md](./reference/terraform.md) |
|
|
56
|
+
| CLI, imperative (`tcld` for Cloud, `temporal operator` for self-hosted) | [tcld.md](./reference/tcld.md) |
|
|
57
|
+
|
|
58
|
+
Route on concrete repo signals, never on a name. Adding a target later (e.g. a `helm.md` for k8s worker deployment) = one reference file + one routing row.
|
|
59
|
+
|
|
60
|
+
**For existing infra, dispatch the shared `project-discovery` subagent** to scan a **bounded slice** (the `.tf` directory or the namespace in scope — never the whole repo) and return a compact summary of existing providers, resource blocks, namespaces, endpoints, and any `.twf` for this domain. Trigger it deliberately; if the slice is unclear, narrow it with the user first. Its contract lives in [project-discovery-subagent.md](../temporal-architect-design/reference/project-discovery-subagent.md) (owned by the design skill, shared across skills; it scans `.tf` as well as `.twf` and code). Consume its summary — don't re-scan in the main context.
|
|
61
|
+
|
|
62
|
+
### 1. Read the Topology
|
|
63
|
+
|
|
64
|
+
From the `.twf` in scope, extract the deployment topology only:
|
|
65
|
+
|
|
66
|
+
- every `namespace` block and its name
|
|
67
|
+
- every `nexus endpoint` and its `worker_target` (the target namespace + `task_queue` it routes to)
|
|
68
|
+
- worker task queues (they inform each endpoint's `worker_target` and confirm which namespace a worker runs in)
|
|
69
|
+
|
|
70
|
+
Ignore workflow/activity bodies — they are `author-go`'s concern, not infrastructure.
|
|
71
|
+
|
|
72
|
+
### 2. Map Topology to Resources
|
|
73
|
+
|
|
74
|
+
This is the contract from `.twf` to control-plane resources:
|
|
75
|
+
|
|
76
|
+
| `.twf` construct | Temporal Cloud (Terraform) | Self-hosted (CLI) |
|
|
77
|
+
|------------------|----------------------------|-------------------|
|
|
78
|
+
| `namespace Name:` | `temporalcloud_namespace` | `temporal operator namespace create` / `tcld namespace create` |
|
|
79
|
+
| `nexus endpoint Name` (`worker_target` = namespace + `task_queue`) | `temporalcloud_nexus_endpoint` (`worker_target { namespace_id, task_queue }`) | `tcld nexus endpoint create --target-namespace --target-task-queue` |
|
|
80
|
+
| worker `task_queue` option | informs the endpoint's `worker_target.task_queue`; not itself a resource | same |
|
|
81
|
+
| custom search attribute *(not yet in `.twf` — see below)* | `temporalcloud_namespace_search_attribute` | `temporal operator search-attribute create` |
|
|
82
|
+
| endpoint access policy *(not yet in `.twf` — see below)* | `allowed_caller_namespaces` on the endpoint | `--allow-namespace` / `tcld nexus endpoint allowed-namespace` |
|
|
83
|
+
|
|
84
|
+
Task queues are **not** standalone resources — they exist implicitly when a worker polls them. The endpoint's `worker_target` is what makes a task queue routable for Nexus.
|
|
85
|
+
|
|
86
|
+
### 3. Provision + Verify
|
|
87
|
+
|
|
88
|
+
Follow the target's reference file. Verify with the target's plan/apply (or dry-run) before applying for real:
|
|
89
|
+
|
|
90
|
+
- **Terraform:** `terraform init` → `terraform plan` (review the diff — especially any unexpected create on a resource that should be imported) → `terraform apply`.
|
|
91
|
+
- **CLI:** run `... list` / `... get` to confirm current state before `create`; re-run after to confirm.
|
|
92
|
+
|
|
93
|
+
For existing infra, `import` before plan so Terraform manages rather than recreates. See each reference for the import workflow.
|
|
94
|
+
|
|
95
|
+
After provisioning: present the resources created/changed and how they line up with the `.twf` topology and the worker code's expectations (task queue names must match what `author-go` registered).
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Not-Yet-Modeled `.twf` Intent
|
|
100
|
+
|
|
101
|
+
Two things the infra author needs are **not yet expressible in `.twf`**:
|
|
102
|
+
|
|
103
|
+
- **Nexus endpoint access policy** — which caller namespaces may invoke an endpoint (`allowed_caller_namespaces` / `--allow-namespace`). Tracked in `dsl/BACKLOG.md` (Nexus Endpoint Access Policy).
|
|
104
|
+
- **Custom search attributes** — namespace-level attribute declarations (name + type). Tracked in `dsl/BACKLOG.md` (Custom Search Attributes).
|
|
105
|
+
|
|
106
|
+
Until the grammar lands, **ask the user** for these (or read an interim annotation if the project uses one) rather than inventing them. Do not default an endpoint to "allow all callers" or guess attribute types — both are security- and schema-relevant. When the DSL backlog items are promoted, this skill consumes them from the topology like any other resource and the asking step drops away.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Output Conventions
|
|
111
|
+
|
|
112
|
+
- Terraform: group resources for one `.twf` design into a module or a single `.tf` file alongside the `.twf` (or where the user's IaC lives); never scatter a design's resources across unrelated state.
|
|
113
|
+
- CLI: produce an ordered, idempotent runbook (`list`/`get` guard → `create`) the user can paste or commit, not a one-off transcript.
|
|
114
|
+
- Name resources after their `.twf` identifiers so the topology↔infra mapping stays legible.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Reference Index
|
|
119
|
+
|
|
120
|
+
Read only the reference for the detected (or chosen) deployment target.
|
|
121
|
+
|
|
122
|
+
| Target | File |
|
|
123
|
+
|--------|------|
|
|
124
|
+
| Temporal Cloud, declarative — Terraform (`temporalio/temporalcloud`) | [terraform.md](./reference/terraform.md) |
|
|
125
|
+
| Imperative CLI — `tcld` (Cloud) / `temporal operator` (self-hosted) | [tcld.md](./reference/tcld.md) |
|
|
126
|
+
|
|
127
|
+
Shared:
|
|
128
|
+
|
|
129
|
+
| Topic | File |
|
|
130
|
+
|-------|------|
|
|
131
|
+
| Bounded-slice repo discovery (existing infra) | [project-discovery-subagent.md](../temporal-architect-design/reference/project-discovery-subagent.md) |
|
|
132
|
+
| Namespace count / boundary decisions (a *design* call, upstream of provisioning) | [namespaces.md](../temporal-architect-design/reference/namespaces.md) |
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# CLI Provisioning (`tcld` / `temporal operator`)
|
|
2
|
+
|
|
3
|
+
The imperative alternative to Terraform. Two distinct CLIs cover two deployment targets:
|
|
4
|
+
|
|
5
|
+
| CLI | Target | Use when |
|
|
6
|
+
|-----|--------|----------|
|
|
7
|
+
| `tcld` | **Temporal Cloud** | You want imperative Cloud provisioning without committing to Terraform state. |
|
|
8
|
+
| `temporal operator` | **Self-hosted** OSS cluster | There is no Cloud account — you run your own `temporal server`. |
|
|
9
|
+
|
|
10
|
+
Prefer [Terraform](./terraform.md) for Temporal Cloud when the project keeps IaC in state; reach for `tcld` for one-off or scripted Cloud changes. For self-hosted there is no Terraform provider, so `temporal operator` is the path.
|
|
11
|
+
|
|
12
|
+
Whichever CLI: **make the runbook idempotent** — guard every `create` with a `list`/`get` so re-running is safe, and commit the runbook rather than leaving provisioning as an untracked transcript.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Namespaces
|
|
17
|
+
|
|
18
|
+
**Temporal Cloud (`tcld`):**
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
tcld namespace create \
|
|
22
|
+
--namespace orders \
|
|
23
|
+
--region aws-us-east-1 \
|
|
24
|
+
--retention-days 14 \
|
|
25
|
+
--auth-method api_key
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Self-hosted (`temporal operator`):**
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
temporal operator namespace create \
|
|
32
|
+
--namespace orders \
|
|
33
|
+
--retention 14d
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Self-hosted namespaces have no region/auth-method flags — those are Cloud concerns. Retention is `--retention` with a duration (`14d`).
|
|
37
|
+
|
|
38
|
+
## Custom Search Attributes
|
|
39
|
+
|
|
40
|
+
**Not yet modeled in `.twf`** — ask the user for the name and type (see SKILL.md → Not-Yet-Modeled Intent).
|
|
41
|
+
|
|
42
|
+
**Self-hosted (`temporal operator`):**
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
temporal operator search-attribute create \
|
|
46
|
+
--namespace orders \
|
|
47
|
+
--name OrderStatus \
|
|
48
|
+
--type Keyword # Bool | Datetime | Double | Int | Keyword | KeywordList | Text
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Temporal Cloud:** register via `tcld namespace` configuration or the operator API for the Cloud namespace; the type set is the same.
|
|
52
|
+
|
|
53
|
+
## Nexus Endpoints
|
|
54
|
+
|
|
55
|
+
Maps from a `.twf` `nexus endpoint` block. `--target-namespace` + `--target-task-queue` are the endpoint's `worker_target`; `--allow-namespace` is the access policy (**not yet in `.twf`** — ask the user which caller namespaces to trust; do not default to allow-all).
|
|
56
|
+
|
|
57
|
+
**Temporal Cloud (`tcld`):**
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Guard, then create.
|
|
61
|
+
tcld nexus endpoint list
|
|
62
|
+
tcld nexus endpoint create \
|
|
63
|
+
--name payments-endpoint \
|
|
64
|
+
--target-namespace payments.<acct> \
|
|
65
|
+
--target-task-queue payments \
|
|
66
|
+
--allow-namespace orders.<acct>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Manage the access policy after creation:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
tcld nexus endpoint allowed-namespace list --name payments-endpoint
|
|
73
|
+
tcld nexus endpoint allowed-namespace add --name payments-endpoint --namespace orders.<acct>
|
|
74
|
+
tcld nexus endpoint allowed-namespace set --name payments-endpoint --namespace orders.<acct> # replaces the full set
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`create` fails if an endpoint of the same name already exists — hence the `list` guard. Use `tcld nexus endpoint update` to change an existing endpoint's target.
|
|
78
|
+
|
|
79
|
+
**Self-hosted (`temporal operator`):**
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
temporal operator nexus endpoint create \
|
|
83
|
+
--name payments-endpoint \
|
|
84
|
+
--target-namespace payments \
|
|
85
|
+
--target-task-queue payments
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Notes:
|
|
89
|
+
- The endpoint `--name` is the identifier caller workflow code uses to invoke the endpoint — it must match the endpoint name in the `.twf` / `author-go` output.
|
|
90
|
+
- The target task queue must match the queue `author-go`'s handler worker polls, or Nexus tasks route nowhere.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Verify
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Cloud
|
|
98
|
+
tcld namespace get --namespace orders
|
|
99
|
+
tcld nexus endpoint get --name payments-endpoint
|
|
100
|
+
|
|
101
|
+
# Self-hosted
|
|
102
|
+
temporal operator namespace describe --namespace orders
|
|
103
|
+
temporal operator nexus endpoint get --name payments-endpoint
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
Confirm each resource exists and its target task queue matches what the workers register on.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Future: `helm.md`
|
|
111
|
+
|
|
112
|
+
Self-hosted **worker deployment** (k8s Helm chart) is a natural future variant — a separate reference + one routing row in SKILL.md. It is worker runtime, distinct from the control-plane resources this file provisions.
|