temporalio/temporal
Patterns and conventions
The repository is large and old enough that it has its own house style. This page collects the conventions that show up in code review and that AGENTS.md explicitly calls out.
fx everywhere
Every service is wired with Uber fx. The pattern is uniform:
- Each package that produces a long-lived dependency exports a
Module = fx.Options(fx.Provide(...)). - Service-level wiring lives in
fx.gofiles:temporal/fx.go,service/history/fx.go,service/matching/fx.go,service/frontend/fx.go,service/worker/fx.go. - The dependency graph is inspectable; use
temporal.WithStartupSynchronizationMode(temporal.Blocking)in tests to wait for fx initialisation.
When adding a new component, expose it via fx.Provide rather than wiring it manually. Lifecycle (OnStart / OnStop) goes through fx.Lifecycle, not via package-level init().
Error handling
From AGENTS.md: "errors MUST be handled, not ignored". The lint rules backstop this with errcheck.
Useful conventions:
logger.Fatal— only for genuinely unrecoverable invariant violations.logger.DPanic— important but should not crash production. Panics in dev/test where it's caught earlier.- Wrap errors with context using
fmt.Errorf("...: %w", err). The serializer side recovers structured errors viaerrors.As. - Service errors — RPC handlers return typed errors from
go.temporal.io/api/serviceerroror the project's owncommon/serviceerror/for server-internal cases. These map to gRPC status codes via the global error encoder. CONSIDER(name):comments — the team's convention for "future design considerations". Use it to flag a thing you've thought about but won't fix in this PR.
Concurrency
- Goroutines should be lifecycle-aware. Use
common/goro/, which gives you a goroutine that joins on shutdown. - Cancellation via context. Pass a
context.Contextfrom the call site; do not usecontext.Background()inside business logic. - Locks — when you need an in-process lock for an entity (workflow, task queue, shard), reuse helpers from
common/locks/. - Atomics —
go.uber.org/atomicis the chosen library; the standardsync/atomicis also fine. Don't roll your own atomic counters. time.Sleepin tests is forbidden — userequire.Eventually.
Persistence access
- All DB access goes through
common/persistence/— never call drivers directly from service code. - The persistence interface is wrapped by metric / retry / rate-limit / fault-injection clients. The order is fixed in
common/persistence/client/andtemporal/fx.go; don't reorder without a maintainer's nod. - For optimistic concurrency, the persistence layer returns
*persistence.ConditionFailedError. Callers retry by reloading state. - For Cassandra-specific concerns, see the implementation under
common/persistence/cassandra/.
RPC and clients
- Clients between services live in
client/. The right way to talk to History isclient.HistoryClient; same for Matching, Frontend, Admin. - Service handlers go through gRPC interceptors generated by
cmd/tools/genrpcserverinterceptors/. Adding a new server-side concern usually means adding an interceptor rather than touching every handler. - HTTP API surface (REST gateway) lives in
service/frontend/http_api_server.go. It generates handlers from a Swagger spec inapi/.
Dynamic configuration
- Anything you might want to tune at runtime — limits, timeouts, feature flags — goes through
common/dynamicconfig/. - Add the constant to one of the per-package files in
common/dynamicconfig/and use the typed accessor (dynamicconfig.NewGlobalIntSetting, etc.). - Document the meaning in the
Descriptionfield; the docs are scraped by tooling. - Default values must be safe enough to ship without operator action.
Metrics
- All metrics are defined in
common/metrics/metric_defs.go. Don't introduce ad-hoc metric names — define them centrally so dashboards stay maintainable. - Use the typed handler from
common/metrics, notprometheus/client_golangdirectly.
Coding style
- Comments explain
why, notwhat— call-out fromAGENTS.md. Avoid restating code in prose. - No new third-party libraries without a maintainer's approval. The repository pulls in plenty already and the bar for additions is high.
- Imports — grouped (stdlib / external / internal) and sorted by
make fmt-imports. - License header — every Go file gets the project header;
make copyrightenforces.
Testing patterns
Already covered on Testing, but two style rules show up in PR reviews:
- Failure modes are first-class. A new piece of functionality without a failure-mode test will likely be sent back.
- No
time.Sleepin tests —require.Eventuallyis the only acceptable form of "wait for…". - Float comparisons use
InDelta/InEpsilon.
Working with the CHASM framework
If you are adding a new state-machine library, follow the layout already used by chasm/lib/scheduler/ and the architecture notes in docs/architecture/chasm.md. Specifically:
- One package per ASM (Application State Machine).
- A
library.goregisters the components and tasks. - A
frontend.goprovides the gRPC handler that maps API calls to Engine operations. - An
fx.gowires the library into the application. - State is declared as
chasm.Field[T]/chasm.Map[K, T]; do not store mutable state in plain Go fields. - Tasks are scheduled via the framework's API, never via raw goroutines.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.