Open-Source Wikis

/

Temporal

/

How to contribute

/

Patterns and conventions

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:

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 via errors.As.
  • Service errors — RPC handlers return typed errors from go.temporal.io/api/serviceerror or the project's own common/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.Context from the call site; do not use context.Background() inside business logic.
  • Locks — when you need an in-process lock for an entity (workflow, task queue, shard), reuse helpers from common/locks/.
  • Atomicsgo.uber.org/atomic is the chosen library; the standard sync/atomic is also fine. Don't roll your own atomic counters.
  • time.Sleep in tests is forbidden — use require.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/ and temporal/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

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 Description field; 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, not prometheus/client_golang directly.

Coding style

  • Comments explain why, not what — call-out from AGENTS.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 copyright enforces.

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.Sleep in testsrequire.Eventually is 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.go registers the components and tasks.
  • A frontend.go provides the gRPC handler that maps API calls to Engine operations.
  • An fx.go wires 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.

Patterns and conventions – Temporal wiki | Factory