Run it locally
Ian — concepts land when you can run them. This lesson is the wiring: the server, the worker, and the starter. It also surfaces the one Go-specific determinism trap that bites everyone. We won't stand up infra in this lesson — that's a hands-on session — but you'll leave able to read the Hello World sample and know what each line does.
The three parts
The worker and the starter are both just Go programs talking to the server. The server is the durable brain — it holds the event history that makes replay possible.
The worker (verified Go, from the docs)
A worker connects to the server, then registers which workflows and activities it can run, then starts polling:
const (
HostPort = "127.0.0.1:7933" // local Cadence
Domain = "research-domain"
TaskListName = "research-tasklist"
)
func main() {
service := buildCadenceClient() // YARPC/tchannel wiring (boilerplate)
w := worker.New(service, Domain, TaskListName, worker.Options{Logger: logger})
// Register everything this worker can run:
w.RegisterWorkflow(ResearchWorkflow)
w.RegisterActivity(CoordinationActivity)
w.RegisterActivity(ExecutionActivity)
// ... the rest of the 7 stages
if err := w.Start(); err != nil { // begins polling the task list
logger.Fatal("worker failed to start")
}
select {} // block forever
}
The three arguments to worker.New — service, Domain, TaskListName — are the address book. Workers poll a task list; the server dispatches work onto it. Everything registered here is what this worker is allowed to execute.
The starter
Starting is separate from the worker. Any program with a client can do it, using client.StartWorkflow with the two mandatory options (TaskList and ExecutionStartToCloseTimeout) plus a business ID:
run, err := cadenceClient.StartWorkflow(ctx,
client.StartWorkflowOptions{
ID: "research-" + topicSlug, // stable business ID
TaskList: TaskListName,
ExecutionStartToCloseTimeout: time.Hour, // whole run's ceiling
},
ResearchWorkflow, // the function (or its registered name)
topic, // workflow argument
)
That ID matters: Cadence guarantees only one open workflow with a given ID per domain (from Lesson 1's docs). Keying it to the research topic gives you free dedup — start the same topic twice and the second is rejected while the first runs.
select
The Go client docs are explicit: workflow code must not use native Go channels or select — they schedule non-deterministically and break replay. Cadence provides deterministic replacements: workflow.Channel instead of chan, and workflow.Selector instead of select. You'll need workflow.Selector in the very next lesson to wait on a human signal. In activities, native channels are fine — determinism only binds workflow code.
The payoff: kill the worker, watch it resume
Here is the demo that makes durable execution real, and the thing your ICM folder model can't do automatically. Once you're running locally:
Contrast with the research-assistant-workflow today: a crash mid-stage means you re-open the stage folder and re-run it by hand. Cadence makes "resume exactly where it stopped" the default, at the cost of running a server and thinking about determinism.
github.com/cadence-workflow/cadence, run its docker-compose (server + Cassandra + web UI), register the domain once with the CLI, then go run the worker and the starter. We'll do this step by step — for now, just hold the three-part shape.
Retrieval check
You Ctrl-C the worker while ExecutionActivity is running. After you restart it, what happens?
Correct. The server holds the event history. A restarted worker gets the parked workflow, replays to rebuild state, and continues — earlier activities are not re-run.
No. The durable state lives in the server, not the worker. A restarted worker replays the history and resumes from the last recorded point.
Your workflow needs to wait on one of several events at once. What do you use?
Correct. Native select and channels are non-deterministic and break replay. Cadence's workflow.Selector and workflow.Channel are the deterministic equivalents for workflow code.
No. Native select, channels, and goroutines are non-deterministic in workflow code. Use workflow.Selector over workflow.Channel instead.
What does the three-argument worker.New(service, Domain, TaskList) plus RegisterWorkflow/RegisterActivity establish?
Correct. The args are the address book (server, domain, task list); Register* declares the workflows/activities this worker can run. Storage is the server's job.
No. That call sets where to connect and what this worker can run. Timeouts are per-activity options, and storage is the server's concern.
New terms — worker, task list, starter, workflow.Selector, workflow.Channel — are in the glossary.