{"id":13542956,"url":"https://github.com/temporalio/sdk-ruby","last_synced_at":"2026-01-26T21:20:06.210Z","repository":{"id":37081129,"uuid":"479006152","full_name":"temporalio/sdk-ruby","owner":"temporalio","description":"Temporal Ruby SDK","archived":false,"fork":false,"pushed_at":"2026-01-15T15:27:52.000Z","size":1985,"stargazers_count":177,"open_issues_count":23,"forks_count":21,"subscribers_count":16,"default_branch":"main","last_synced_at":"2026-01-22T01:53:25.114Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/temporalio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-04-07T13:51:06.000Z","updated_at":"2026-01-21T04:41:55.000Z","dependencies_parsed_at":"2024-01-16T17:02:56.284Z","dependency_job_id":"ba03d880-2313-462f-9d47-2b5c771569ae","html_url":"https://github.com/temporalio/sdk-ruby","commit_stats":{"total_commits":57,"total_committers":3,"mean_commits":19.0,"dds":0.08771929824561409,"last_synced_commit":"f8897ca9992dad2424493f8992d5860ed9da25f5"},"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/temporalio/sdk-ruby","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/temporalio%2Fsdk-ruby","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/temporalio%2Fsdk-ruby/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/temporalio%2Fsdk-ruby/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/temporalio%2Fsdk-ruby/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/temporalio","download_url":"https://codeload.github.com/temporalio/sdk-ruby/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/temporalio%2Fsdk-ruby/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28788368,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T21:13:08.818Z","status":"ssl_error","status_checked_at":"2026-01-26T21:13:08.448Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-01T11:00:20.393Z","updated_at":"2026-01-26T21:20:06.180Z","avatar_url":"https://github.com/temporalio.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":["Videos"],"readme":"\u003cdiv style=\"overflow: hidden\"\u003e\u003cimg src=\"https://raw.githubusercontent.com/temporalio/assets/main/files/w/ruby.png\" alt=\"Temporal Ruby SDK\" /\u003e\u003c/div\u003e\n\n![Ruby 3.3 | 3.4 | 4.0](https://img.shields.io/badge/ruby-3.3%20|%203.4%20|%204.0-blue.svg?style=for-the-badge)\n[![MIT](https://img.shields.io/github/license/temporalio/sdk-ruby.svg?style=for-the-badge)](LICENSE)\n[![Gem](https://img.shields.io/gem/v/temporalio?style=for-the-badge)](https://rubygems.org/gems/temporalio)\n\n[Temporal](https://temporal.io/) is a distributed, scalable, durable, and highly available orchestration engine used to\nexecute asynchronous, long-running business logic in a scalable and resilient way.\n\n**Temporal Ruby SDK** is the framework for authoring workflows and activities using the Ruby programming language.\n\nAlso see:\n\n* [Ruby SDK](https://github.com/temporalio/sdk-ruby)\n* [Ruby Samples](https://github.com/temporalio/samples-ruby)\n* [API Documentation](https://ruby.temporal.io)\n\n**NOTE: This README is for the current branch and not necessarily what's released on RubyGems.**\n\n---\n\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n**Contents**\n\n- [Quick Start](#quick-start)\n  - [Installation](#installation)\n  - [Implementing a Workflow and Activity](#implementing-a-workflow-and-activity)\n  - [Running a Worker](#running-a-worker)\n  - [Executing a Workflow](#executing-a-workflow)\n- [Usage](#usage)\n  - [Client](#client)\n    - [Cloud Client Using mTLS](#cloud-client-using-mtls)\n    - [Cloud Client Using API Key](#cloud-client-using-api-key)\n    - [Data Conversion](#data-conversion)\n      - [ActiveModel](#activemodel)\n      - [Converter Hints](#converter-hints)\n  - [Workers](#workers)\n  - [Workflows](#workflows)\n    - [Workflow Definition](#workflow-definition)\n    - [Running Workflows](#running-workflows)\n    - [Invoking Activities](#invoking-activities)\n    - [Invoking Child Workflows](#invoking-child-workflows)\n    - [Timers and Conditions](#timers-and-conditions)\n    - [Workflow Fiber Scheduling and Cancellation](#workflow-fiber-scheduling-and-cancellation)\n    - [Workflow Futures](#workflow-futures)\n    - [Workflow Utilities](#workflow-utilities)\n    - [Workflow Exceptions](#workflow-exceptions)\n    - [Workflow Logic Constraints](#workflow-logic-constraints)\n    - [Workflow Testing](#workflow-testing)\n      - [Automatic Time Skipping](#automatic-time-skipping)\n      - [Manual Time Skipping](#manual-time-skipping)\n      - [Mocking Activities](#mocking-activities)\n    - [Workflow Replay](#workflow-replay)\n    - [Advanced Workflow Safety and Escaping](#advanced-workflow-safety-and-escaping)\n      - [Durable Fiber Scheduler](#durable-fiber-scheduler)\n      - [Illegal Call Tracing](#illegal-call-tracing)\n  - [Activities](#activities)\n    - [Activity Definition](#activity-definition)\n    - [Activity Context](#activity-context)\n    - [Activity Heartbeating and Cancellation](#activity-heartbeating-and-cancellation)\n    - [Activity Worker Shutdown](#activity-worker-shutdown)\n    - [Activity Concurrency and Executors](#activity-concurrency-and-executors)\n    - [Activity Testing](#activity-testing)\n  - [Telemetry](#telemetry)\n    - [Metrics](#metrics)\n    - [OpenTelemetry Tracing](#opentelemetry-tracing)\n      - [OpenTelemetry Tracing in Workflows](#opentelemetry-tracing-in-workflows)\n  - [Rails](#rails)\n    - [ActiveRecord](#activerecord)\n    - [Lazy/Eager Loading](#lazyeager-loading)\n  - [Forking](#forking)\n  - [Ractors](#ractors)\n  - [Platform Support](#platform-support)\n  - [Migration from Coinbase Ruby SDK](#migration-from-coinbase-ruby-sdk)\n- [Development](#development)\n  - [Build](#build)\n    - [Build Platform-specific Gem](#build-platform-specific-gem)\n  - [Testing](#testing)\n  - [Code Formatting and Type Checking](#code-formatting-and-type-checking)\n  - [Proto Generation](#proto-generation)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Quick Start\n\n### Installation\n\nThe Ruby SDK works with Ruby 3.3, 3.4, and 4.0.\n\nCan require in a Gemfile like:\n\n```\ngem 'temporalio'\n```\n\nOr via `gem install` like:\n\n```\ngem install temporalio\n```\n\n**NOTE**: Only macOS ARM/x64 and Linux ARM/x64 are supported, and the platform-specific gem chosen is based on when the\ngem/bundle install is performed. A source gem is published but cannot be used directly and will fail to build if tried.\nMinGW-based Windows is not currently supported. There are caveats with the Google Protobuf dependency on musl-based\nLinux. See the [Platform Support](#platform-support) section for more information.\n\n**NOTE**: Due to [an issue](https://github.com/temporalio/sdk-ruby/issues/162), fibers (and `async` gem) are only\nsupported on Ruby versions 3.3 and newer.\n\n### Implementing a Workflow and Activity\n\nActivities are classes. Here is an example of a simple activity that can be put in `say_hello_activity.rb`:\n\n\n```ruby\nrequire 'temporalio/activity'\n\n# Implementation of a simple activity\nclass SayHelloActivity \u003c Temporalio::Activity::Definition\n  def execute(name)\n    \"Hello, #{name}!\"\n  end\nend\n```\n\nWorkflows are also classes. To create the workflow, put the following in `say_hello_workflow.rb`:\n\n```ruby\nrequire 'temporalio/workflow'\nrequire_relative 'say_hello_activity'\n\nclass SayHelloWorkflow \u003c Temporalio::Workflow::Definition\n  def execute(name)\n    Temporalio::Workflow.execute_activity(\n      SayHelloActivity,\n      name,\n      schedule_to_close_timeout: 300\n    )\n  end\nend\n```\n\nThis is a simple workflow that executes the `SayHelloActivity` activity.\n\n### Running a Worker\n\nTo run this in a worker, put the following in `worker.rb`:\n\n```ruby\nrequire 'temporalio/client'\nrequire 'temporalio/worker'\nrequire_relative 'say_hello_activity'\nrequire_relative 'say_hello_workflow'\n\n# Create a client\nclient = Temporalio::Client.connect('localhost:7233', 'my-namespace')\n\n# Create a worker with the client, activities, and workflows\nworker = Temporalio::Worker.new(\n  client:,\n  task_queue: 'my-task-queue',\n  workflows: [SayHelloWorkflow],\n  # There are various forms an activity can take, see \"Activities\" section for details\n  activities: [SayHelloActivity]\n)\n\n# Run the worker until SIGINT. This can be done in many ways, see \"Workers\" section for details.\nworker.run(shutdown_signals: ['SIGINT'])\n```\n\nRunning that will run the worker until Ctrl+C is pressed.\n\n### Executing a Workflow\n\nTo start and wait on the workflow result, with the worker program running elsewhere, put the following in\n`execute_workflow.rb`:\n\n```ruby\nrequire 'temporalio/client'\nrequire_relative 'say_hello_workflow'\n\n# Create a client\nclient = Temporalio::Client.connect('localhost:7233', 'my-namespace')\n\n# Run workflow\nresult = client.execute_workflow(\n  SayHelloWorkflow,\n  'Temporal', # This is the input to the workflow\n  id: 'my-workflow-id',\n  task_queue: 'my-task-queue'\n)\nputs \"Result: #{result}\"\n```\n\nThis will output:\n\n```\nResult: Hello, Temporal!\n```\n\n## Usage\n\n### Client\n\nA client can be created and used to start a workflow or otherwise interact with Temporal. For example:\n\n```ruby\nrequire 'temporalio/client'\n\n# Create a client\nclient = Temporalio::Client.connect('localhost:7233', 'my-namespace')\n\n# Start a workflow\nhandle = client.start_workflow(\n  MyWorkflow,\n  'arg1', 'arg2',\n  id: 'my-workflow-id',\n  task_queue: 'my-task-queue'\n)\n\n# Wait for result\nresult = handle.result\nputs \"Result: #{result}\"\n```\n\nNotes about the above code:\n\n* Temporal clients are not explicitly closed.\n* To enable TLS, the `tls` option can be set to `true` or a `Temporalio::Client::Connection::TLSOptions` instance.\n* Instead of `start_workflow` + `result` above, `execute_workflow` shortcut can be used if the handle is not needed.\n* Both `start_workflow` and `execute_workflow` accept either the workflow class or the string/symbol name of the\n  workflow.\n* The `handle` above is a `Temporalio::Client::WorkflowHandle` which has several other operations that can be performed\n  on a workflow. To get a handle to an existing workflow, use `workflow_handle` on the client.\n* Clients are thread safe and are fiber-compatible (but fiber compatibility only supported for Ruby 3.3+ at this time).\n\n#### Cloud Client Using mTLS\n\nAssuming a client certificate is present at `my-cert.pem` and a client key is present at `my-key.pem`, this is how to\nconnect to Temporal Cloud:\n\n```ruby\nrequire 'temporalio/client'\n\n# Create a client\nclient = Temporalio::Client.connect(\n  'my-namespace.a1b2c.tmprl.cloud:7233',\n  'my-namespace.a1b2c',\n  tls: Temporalio::Client::Connection::TLSOptions.new(\n    client_cert: File.read('my-cert.pem'),\n    client_private_key: File.read('my-key.pem')\n  ))\n```\n\n#### Cloud Client Using API Key\n\nAssuming the API key is 'my-api-key', this is how to connect to Temporal cloud:\n\n```ruby\nrequire 'temporalio/client'\n\n# Create a client\nclient = Temporalio::Client.connect(\n  'my-namespace.a1b2c.tmprl.cloud:7233',\n  'my-namespace.a1b2c',\n  api_key: 'my-api-key'\n  tls: true\n)\n```\n\n#### Data Conversion\n\nData converters are used to convert raw Temporal payloads to/from actual Ruby types. A custom data converter can be set\nvia the `data_converter` keyword argument when creating a client. Data converters are a combination of payload\nconverters, payload codecs, and failure converters. Payload converters convert Ruby values to/from serialized bytes.\nPayload codecs convert bytes to bytes (e.g. for compression or encryption). Failure converters convert exceptions\nto/from serialized failures.\n\nData converters are in the `Temporalio::Converters` module. The default data converter uses a default payload converter,\nwhich supports the following types:\n\n* `nil`\n* \"bytes\" (i.e. `String` with `Encoding::ASCII_8BIT` encoding)\n* `Google::Protobuf::MessageExts` instances\n* [JSON module](https://docs.ruby-lang.org/en/master/JSON.html) for everything else\n\nThis means that normal Ruby objects will use `JSON.generate` when serializing and `JSON.parse` when deserializing (with\n`create_additions: true` set by default). So a Ruby object will often appear as a hash when deserialized. Also, hashes\nthat are passed in with symbol keys end up with string keys when deserialized. While \"JSON Additions\" are supported, it\nis not cross-SDK-language compatible since this is a Ruby-specific construct.\n\nThe default payload converter is a collection of \"encoding payload converters\". On serialize, each encoding converter\nwill be tried in order until one accepts (default falls through to the JSON one). The encoding converter sets an\n`encoding` metadata value which is used to know which converter to use on deserialize. Custom encoding converters can be\ncreated, or even the entire payload converter can be replaced with a different implementation.\n\n**NOTE:** For ActiveRecord, or other general/ORM models that are used for a different purpose, it is not recommended to\ntry to reuse them as Temporal models. Eventually model purposes diverge and models for a Temporal workflows/activities\nshould be specific to their use for clarity and compatibility reasons. Also many Ruby ORMs do many lazy things and\ntherefore provide unclear serialization semantics. Instead, consider having models specific for workflows/activities and\ntranslate to/from existing models as needed. See the next section on how to do this with ActiveModel objects.\n\n##### ActiveModel\n\nBy default, ActiveModel objects do not natively support the `JSON` module. A mixin can be created to add this support\nfor ActiveRecord, for example:\n\n```ruby\nmodule ActiveModelJSONSupport\n  extend ActiveSupport::Concern\n  include ActiveModel::Serializers::JSON\n\n  included do\n    def as_json(*)\n      super.merge(::JSON.create_id =\u003e self.class.name)\n    end\n\n    def to_json(*args)\n      as_json.to_json(*args)\n    end\n\n    def self.json_create(object)\n      object = object.dup\n      object.delete(::JSON.create_id)\n      new(**object.symbolize_keys)\n    end\n  end\nend\n```\n\nNow if `include ActiveModelJSONSupport` is present on any ActiveModel class, on serialization `to_json` will be used\nwhich will use `as_json` which calls the super `as_json` but also includes the fully qualified class name as the JSON\n`create_id` key. On deserialization, Ruby JSON then uses this key to know what class to call `json_create` on.\n\n##### Converter Hints\n\nIn most places where objects are converted to payloads or vice versa, a \"hint\" can be provided to tell the converter\nsomething else about the object/payload to assist conversion. The default converters ignore these hints, but custom\nconverters can be written to take advantage of them. For example, hints may be used to provide a custom converter the\nRuby type to deserialize a payload into.\n\nThese hints manifest themselves various ways throughout the API. The most obvious way is when making definitions. An\nactivity can define `activity_arg_hint` (which accepts multiple) and/or `activity_result_hint` for activity-level hints.\nSimilarly, a workflow can define `workflow_arg_hint` and/or `workflow_result_hint` for workflow-level hints.\n`workflow_signal`, `workflow_query`, and `workflow_update` all similarly accept `arg_hints` and `result_hint` (except\nsignal of course). These definition-level hints are passed to converters both from the caller side and the\nimplementation side.\n\nThere are some advanced payload uses in the SDK that do not currently have a way to set hints. These include\nworkflow/schedule memo, workflow get/upsert memo, and application error details. In some cases, users can use\n`Temporalio::Converters::RawValue` and then manually convert with hints. For others, hints can be added as needed,\nplease open an issue or otherwise contact Temporal.\n\n### Workers\n\nWorkers host workflows and/or activities. Here's how to run a worker:\n\n```ruby\nrequire 'temporalio/client'\nrequire 'temporalio/worker'\nrequire 'my_module'\n\n# Create a client\nclient = Temporalio::Client.connect('localhost:7233', 'my-namespace')\n\n# Create a worker with the client, activities, and workflows\nworker = Temporalio::Worker.new(\n  client:,\n  task_queue: 'my-task-queue',\n  workflows: [MyModule::MyWorkflow],\n  # There are various forms an activity can take, see \"Activities\" section for details\n  activities: [MyModule::MyActivity]\n)\n\n# Run the worker until block complete\nworker.run do\n  something_that_waits_for_completion\nend\n```\n\nNotes about the above code:\n\n* A worker uses the same client that is used for other Temporal things.\n* This just shows providing an activity class, but there are other forms, see the \"Activities\" section for details.\n* The worker `run` method accepts an optional `Temporalio::Cancellation` object that can be used to cancel instead or in\n  addition to providing a block that waits for completion.\n* The worker `run` method accepts a `shutdown_signals` array which will trap the signal and start shutdown when\n  received.\n* Workers work with threads or fibers (but fiber compatibility only supported for Ruby 3.3+ at this time). Fiber-based\n  activities (see \"Activities\" section) only work if the worker is created within a fiber.\n* The `run` method does not return until the worker is shut down. This means even if shutdown is triggered (e.g. via\n  `Cancellation` or block completion), it may not return immediately. Activities not completing may hang worker\n  shutdown, see the \"Activities\" section.\n* Workers can have many more options not shown here (e.g. tuners and interceptors).\n* The `Temporalio::Worker.run_all` class method is available for running multiple workers concurrently.\n\n### Workflows\n\n#### Workflow Definition\n\nWorkflows are defined as classes that extend `Temporalio::Workflow::Definition`. The entry point for a workflow is\n`execute` and must be defined. Methods for handling signals, queries, and updates are marked with `workflow_signal`,\n`workflow_query`, and `workflow_update` just before the method is defined. Here is an example of a workflow definition:\n\n```ruby\nrequire 'temporalio/workflow'\n\nclass GreetingWorkflow \u003c Temporalio::Workflow::Definition\n  workflow_query_attr_reader :current_greeting\n\n  def execute(params)\n    loop do\n      # Call activity called CreateGreeting to create greeting and store as attribute\n      @current_greeting = Temporalio::Workflow.execute_activity(\n        CreateGreeting,\n        params,\n        schedule_to_close_timeout: 300\n      )\n      Temporalio::Workflow.logger.debug(\"Greeting set to #{@current_greeting}\")\n\n      # Wait for param update or complete signal. Note, cancellation can occur by default\n      # on wait_condition calls, so Cancellation object doesn't need to be passed\n      # explicitly.\n      Temporalio::Workflow.wait_condition { @greeting_params_update || @complete }\n\n      # If there was an update, exchange and rerun. If it's _only_ a complete, finish\n      # workflow with the greeting.\n      if @greeting_params_update\n        params, @greeting_params_update = @greeting_params_update, nil\n      else\n        return @current_greeting\n      end\n    end\n  end\n\n  workflow_update\n  def update_greeting_params(greeting_params_update)\n    @greeting_params_update = greeting_params_update\n  end\n\n  workflow_signal\n  def complete_with_greeting\n    @complete = true\n  end\nend\n```\n\nNotes about the above code:\n\n* `execute` is the primary entrypoint and its result/exception represents the workflow result/failure.\n* `workflow_signal`, `workflow_query` (and the shortcut seen above, `workflow_query_attr_reader`), and `workflow_update`\n  implicitly create class methods usable by callers/clients. A workflow definition with no methods actually implemented\n  can even be created for use by clients if the workflow is implemented elsewhere and/or in another language.\n* Workflow code must be deterministic. See the \"Workflow Logic Constraints\" section below.\n* `execute_activity` accepts either the activity class or the string/symbol for the name.\n\nThe following protected class methods are available on `Temporalio::Workflow::Definition` to customize the overall\nworkflow definition/behavior:\n\n* `workflow_name` - Accepts a string or symbol to change the name. Otherwise the name is defaulted to the unqualified\n  class name.\n* `workflow_dynamic` - Marks a workflow as dynamic. Dynamic workflows do not have names and handle any workflow that is\n  not otherwise registered. A worker can only have one dynamic workflow. It is often useful to use `workflow_raw_args`\n  with this.\n* `workflow_raw_args` - Have workflow arguments delivered to `execute` (and `initialize` if `workflow_init` in use) as\n  `Temporalio::Converters::RawValue`s. These are wrappers for the raw payloads that have not been decoded. They can be\n  decoded with `Temporalio::Workflow.payload_converter`. Using this with `*args` splat can be helpful in dynamic\n  situations.\n* `workflow_failure_exception_type` - Accepts one or more exception classes that will be considered workflow failure\n  instead of task failure. See the \"Exceptions\" section later on what this means. This can be called multiple times.\n* `workflow_query_attr_reader` - Is a helper that accepts one or more symbols for attributes to expose as `attr_reader`\n  _and_ `workflow_query`. This means it is a superset of `attr_reader` and will not work if also using `attr_reader` or\n  `attr_accessor`. If a writer is needed alongside this, use `attr_writer`.\n\nThe following protected class methods can be called just before defining instance methods to customize the\ndefinition/behavior of the method:\n\n* `workflow_init` - Mark an `initialize` method as needing the workflow start arguments. Otherwise, `initialize` must\n  accept no required arguments. This must be placed above the `initialize` method or it will fail.\n* `workflow_signal` - Mark the next method as a workflow signal. The signal name is defaulted to the method name but can\n  be customized by the `name` kwarg. See the API documentation for more kwargs that can be set. Return values for\n  signals are discarded and exceptions raised in signal handlers are treated as if they occurred in the primary workflow\n  method. This also defines a class method of the same name to return the definition for use by clients.\n* `workflow_query` - Mark the next method as a workflow query. The query name is defaulted to the method name but can\n  be customized by the `name` kwarg. See the API documentation for more kwargs that can be set. The result of the method\n  is the result of the query. Queries must never have any side effects, meaning they should never mutate state or try to\n  wait on anything. This also defines a class method of the same name to return the definition for use by clients.\n* `workflow_update` - Mark the next method as a workflow update. The update name is defaulted to the method name but can\n  be customized by the `name` kwarg. See the API documentation for more kwargs that can be set. The result of the method\n  is the result of the update. This also defines a class method of the same name to return the definition for use by\n  clients.\n* `workflow_update_validator` - Mark the next method as a validator to an update. This accepts a symbol for the\n  `workflow_update` method it validates. Validators are used to do early rejection of updates and must never have any\n  side effects, meaning they should never mutate state or try to wait on anything.\n\nWorkflows can be inherited, but subclass workflow-level decorators override superclass ones, and the same method can't\nbe decorated with different handler types/names in the hierarchy. Workflow handlers (execute or any marked method)\ncannot accept keyword arguments.\n\n#### Running Workflows\n\nTo start a workflow from a client, you can `start_workflow` and use the resulting handle:\n\n```ruby\n# Start the workflow\nhandle = my_client.start_workflow(\n  GreetingWorkflow,\n  { salutation: 'Hello', name: 'Temporal' },\n  id: 'my-workflow-id',\n  task_queue: 'my-task-queue'\n)\n\n# Check current greeting via query\nputs \"Current greeting: #{handle.query(GreetingWorkflow.current_greeting)}\"\n\n# Change the params via update\nhandle.execute_update(\n  GreetingWorkflow.update_greeting_params,\n  { salutation: 'Aloha', name: 'John' }\n)\n\n# Tell it to complete via signal\nhandle.signal(GreetingWorkflow.complete_with_greeting)\n\n# Wait for workflow result\nputs \"Final greeting: #{handle.result}\"\n```\n\nSome things to note about the above code:\n\n* This uses the `GreetingWorkflow` workflow from the previous section.\n* The output of this code is \"Current greeting: Hello, Temporal!\" and \"Final greeting: Aloha, John!\".\n* ID and task queue are required for starting a workflow.\n* Signal, query, and update calls here use the class methods created on the definition for safety. So if the\n  `update_greeting_params` method didn't exist or wasn't marked as an update, the code will fail client side before even\n  attempting the call. Static typing tooling may also take advantage of this for param/result type checking.\n* A helper `execute_workflow` method is available on the client that is just `start_workflow` + handle `result`.\n\n#### Invoking Activities\n\n* Activities are executed with `Temporalio::Workflow.execute_activity`, which accepts the activity class or a\n  string/symbol activity name.\n* Activity options are kwargs on the `execute_activity` method. Either `schedule_to_close_timeout` or\n  `start_to_close_timeout` must be set.\n* Other options like `retry_policy`, `cancellation_type`, etc can also be set.\n* The `cancellation` can be set to a `Cancellation` to send a cancel request to the activity. By default, the\n  `cancellation` is the overall `Temporalio::Workflow.cancellation` which is the overarching workflow cancellation.\n* Activity failures are raised from the call as `Temporalio::Error::ActivityError`.\n* `execute_local_activity` exists with mostly the same options for local activities.\n\n#### Invoking Child Workflows\n\n* Child workflows are started with `Temporalio::Workflow.start_child_workflow`, which accepts the workflow class or\n  string/symbol name, arguments, and other options.\n* Result for `start_child_workflow` is a `Temporalio::Workflow::ChildWorkflowHandle` which has the `id`, the ability to\n  wait on the `result`, and the ability to `signal` the child.\n* The `start_child_workflow` call does not complete until the start has been accepted by the server.\n* A helper `execute_child_workflow` method is available that is just `start_child_workflow` + handle `result`.\n\n#### Timers and Conditions\n\n* A timer is represented by `Temporalio::Workflow.sleep`.\n  * Timers are also started on `Temporalio::Workflow.timeout`.\n  * `Kernel.sleep` and `Timeout.timeout` are considered illegal by default.\n  * Each timer accepts a `Cancellation`, but if none is given, it defaults to `Temporalio::Workflow.cancellation`.\n* `Temporalio::Workflow.wait_condition` accepts a block that waits until the evaluated block result is truthy, then\n  returns the value.\n  * This function is invoked on each iteration of the internal event loop. This means it cannot have any side effects.\n  * This is commonly used for checking if a variable is changed from some other part of a workflow (e.g. a signal\n    handler).\n  * Each wait conditions accepts a `Cancellation`, but if none is given, it defaults to\n    `Temporalio::Workflow.cancellation`.\n\n#### Workflow Fiber Scheduling and Cancellation\n\nWorkflows are backed by a custom, deterministic `Fiber::Scheduler`. All fiber calls inside a workflow use this scheduler\nto ensure coroutines run deterministically. Although this means that `Kernel.sleep` and `Mutex` and such should work and\nsince they are Fiber-aware, Temporal intentionally disables their use by default to prevent accidental use. See\n\"Workflow Logic Constraints\" and \"Advanced Workflow Safety and Escaping\" for more details, and see \"Workflow Utilities\"\nfor alternatives.\n\nEvery workflow contains a `Temporalio::Cancellation` at `Temporalio::Workflow.cancellation`. This is canceled when the\nworkflow is canceled. For all workflow calls that accept a cancellation token, this is the default. So if a workflow is\nwaiting on `execute_activity` and the workflow is canceled, that cancellation will propagate to the waiting activity.\n\n`Cancellation`s may be created to perform cancellation more specifically. A `Cancellation` token derived from the\nworkflow one can be created via `my_cancel, my_cancel_proc = Cancellation.new(Temporalio::Workflow.cancellation)`. Then\n`my_cancel` can be passed as `cancellation` to cancel something more specifically when `my_cancel_proc.call` is invoked.\n\n`Cancellation`s don't have to be derived from the workflow one, they can just be created standalone or \"detached\". This\nis useful for executing, say, a cleanup activity in an `ensure` block that needs to run even on cancel. If the cleanup\nactivity had instead used the workflow cancellation or one derived from it, then on cancellation it would be cancelled\nbefore it even started.\n\n#### Workflow Futures\n\n`Temporalio::Workflow::Future` can be used for running things in the background or concurrently. This is basically a\nsafe wrapper around `Fiber.schedule` for starting and `Workflow.wait_condition` for waiting.\n\nNothing uses futures by default, but they work with all workflow code/constructs. For instance, to run 3 activities and\nwait for them all to complete, something like this can be written:\n\n```ruby\n# Start 3 activities in background\nfut1 = Temporalio::Workflow::Future.new do\n  Temporalio::Workflow.execute_activity(MyActivity1, schedule_to_close_timeout: 300)\nend\nfut2 = Temporalio::Workflow::Future.new do\n  Temporalio::Workflow.execute_activity(MyActivity2, schedule_to_close_timeout: 300)\nend\nfut3 = Temporalio::Workflow::Future.new do\n  Temporalio::Workflow.execute_activity(MyActivity3, schedule_to_close_timeout: 300)\nend\n\n# Wait for them all to complete\nTemporalio::Workflow::Future.all_of(fut1, fut2, fut3).wait\n\nTemporalio::Workflow.logger.debug(\"Got: #{fut1.result}, #{fut2.result}, #{fut3.result}\")\n```\n\nOr, say, to wait on the first of 5 activities or a timeout to complete:\n\n```ruby\n# Start 5 activities\nact_futs = 5.times.map do |i|\n  Temporalio::Workflow::Future.new do\n    Temporalio::Workflow.execute_activity(MyActivity, \"my-arg-#{i}\", schedule_to_close_timeout: 300)\n  end\nend\n# Start a timer\nsleep_fut = Temporalio::Workflow::Future.new { Temporalio::Workflow.sleep(30) }\n\n# Wait for first act result or sleep fut\nact_result = Temporalio::Workflow::Future.any_of(sleep_fut, *act_futs).wait\n# Fail if timer done first\nraise Temporalio::Error::ApplicationError, 'Timer expired' if sleep_fut.done?\n# Print act result otherwise\nputs \"Act result: #{act_result}\"\n```\n\nThere are several other details not covered here about futures, such as how exceptions are handled, how to use a setter\nproc instead of a block, etc. See the API documentation for details.\n\n#### Workflow Utilities\n\nIn addition to the pieces documented above, additional methods are available on `Temporalio::Workflow` that can be used\nfrom workflows including:\n\n* `in_workflow?` - Returns `true` if in the workflow or `false` otherwise. This is the only method on the class that can\n  be called outside of a workflow without raising an exception.\n* `info` - Immutable workflow information.\n* `logger` - A Ruby logger that adds contextual information and takes care not to log on replay.\n* `metric_meter` - A metric meter for making custom metrics that adds contextual information and takes care not to\n  record on replay.\n* `random` - A deterministic `Random` instance.\n* `memo` - A read-only hash of the memo (updated via `upsert_memo`).\n* `search_attributes` - A read-only `SearchAttributes` collection (updated via `upsert_search_attributes`).\n* `now` - Current, deterministic UTC time for the workflow.\n* `all_handlers_finished?` - Returns true when all signal and update handlers are done. Useful as\n  `Temporalio::Workflow.wait_condition { Temporalio::Workflow.all_handlers_finished? }` for making sure not to return\n  from the primary workflow method until all handlers are done.\n* `patched` and `deprecate_patch` - Support for patch-based versioning inside the workflow.\n* `continue_as_new_suggested` - Returns true when the server recommends performing a continue as new.\n* `current_update_info` - Returns `Temporalio::Workflow::UpdateInfo` if the current code is inside an update, or nil\n  otherwise.\n* `external_workflow_handle` - Obtain an handle to an external workflow for signalling or cancelling.\n* `payload_converter` - Payload converter if needed for converting raw args.\n* `signal_handlers`, `query_handlers`, and `update_handlers` - Hashes for the current set of handlers keyed by name (or\n  nil key for dynamic). `[]=` or `store` can be called on these to update the handlers, though defined handlers are\n  encouraged over runtime-set ones.\n\nThere are also classes for `Temporalio::Workflow::Mutex`, `Temporalio::Workflow::Queue`, and\n`Temporalio::Workflow::SizedQueue` that are workflow-safe wrappers around the standard library forms.\n\n`Temporalio::Workflow::ContinueAsNewError` can be raised to continue-as-new the workflow. It accepts positional args and\ndefaults the workflow to the same as the current, though it can be changed with the `workflow` kwarg. See API\ndocumentation for other details.\n\n#### Workflow Exceptions\n\n* Workflows can raise exceptions to fail the workflow/update or the \"workflow task\" (i.e. suspend the workflow, retrying\n  until code update allows it to continue).\n* By default, exceptions that are instances of `Temporalio::Error::Failure` (or `Timeout::Error`) will fail the\n  workflow/update with that exception.\n  * For failing the workflow/update explicitly with a user exception, explicitly raise\n    `Temporalio::Error::ApplicationError`. This can be marked non-retryable or include details as needed.\n  * Other exceptions that come from activity execution, child execution, cancellation, etc are already instances of\n    `Temporalio::Error::Failure` and will fail the workflow/update if uncaught.\n* By default, all other exceptions fail the \"workflow task\" which means the workflow/update will continually retry until\n  the code is fixed. This is helpful for bad code or other non-predictable exceptions. To actually fail the\n  workflow/update, use `Temporalio::Error::ApplicationError` as mentioned above.\n* By default, all non-deterministic exceptions that are detected internally fail the \"workflow task\".\n\nThe default behavior can be customized at the worker level for all workflows via the\n`workflow_failure_exception_types` worker option or per workflow via the `workflow_failure_exception_type` definition\nmethod on the workflow itself. When a workflow encounters a \"workflow task\" fail (i.e. suspend), it will first check\neither of these collections to see if the exception is an instance of any of the types and if so, will turn into a\nworkflow/update failure. As a special case, when a non-deterministic exception occurs and\n`Temporalio::Workflow::NondeterminismError` is assignable to any of the types in the collection, that too\nwill turn into a workflow/update failure. However unlike other exceptions, non-deterministic exceptions that match\nduring update handlers become workflow failures not update failures because a non-deterministic exception is an\nentire-workflow-failure situation.\n\n#### Workflow Logic Constraints\n\nTemporal Workflows [must be deterministic](https://docs.temporal.io/workflows#deterministic-constraints), which includes\nRuby workflows. This means there are several things workflows cannot do such as:\n\n* Perform IO (network, disk, stdio, etc)\n* Access/alter external mutable state\n* Do any threading or blocking calls\n* Do anything using the system clock (e.g. `Time.Now`)\n* Make any random calls\n* Make any not-guaranteed-deterministic calls\n\nThis means you can't even use logger calls outside of `Temporalio::Workflow.logger` because they use mutexes which may\nbe hit during periods of high-contention, but they are not completely disabled since users may do quick debugging with\nthem. See the [Advanced Workflow Safety and Escaping](#advanced-workflow-safety-and-escaping) section if needing to work\naround this.\n\n#### Workflow Testing\n\nWorkflow testing can be done in an integration-test fashion against a real server. However, it is hard to simulate\ntimeouts and other long time-based code. Using the time-skipping workflow test environment can help there.\n\nA non-time-skipping `Temporalio::Testing::WorkflowEnvironment` can be started via `start_local` which supports all\nstandard Temporal features. It is actually a real Temporal server lazily downloaded on first use and run as a\nsubprocess in the background.\n\nA time-skipping `Temporalio::Testing::WorkflowEnvironment` can be started via `start_time_skipping` which is a\nreimplementation of the Temporal server with special time skipping capabilities. This too lazily downloads the process\nto run when first called. Note, this class is not thread safe nor safe for use with independent tests. It can be reused,\nbut only for one test at a time because time skipping is locked/unlocked at the environment level. Note, the\ntime-skipping test server does not work on ARM-based processors at this time, though macOS ARM users can use it via the\nbuilt-in x64 translation in macOS.\n\n##### Automatic Time Skipping\n\nAnytime a workflow result is waited on, the time-skipping server automatically advances to the next event it can. To\nmanually advance time before waiting on the result of the workflow, the `WorkflowEnvironment.sleep` method can be used\non the environment itself. If an activity is running, time-skipping is disabled.\n\nHere's a simple example of a workflow that sleeps for 24 hours:\n\n```ruby\nrequire 'temporalio/workflow'\n\nclass WaitADayWorkflow \u003c Temporalio::Workflow::Definition\n  def execute\n    Temporalio::Workflow.sleep(1 * 24 * 60 * 60)\n    'all done'\n  end\nend\n```\n\nA regular integration test of this workflow on a normal server would be way too slow. However, the time-skipping server\nautomatically skips to the next event when we wait on the result. Here's a minitest for that workflow:\n\n```ruby\nclass MyTest \u003c Minitest::Test\n  def test_wait_a_day\n    Temporalio::Testing::WorkflowEnvironment.start_time_skipping do |env|\n      worker = Temporalio::Worker.new(\n        client: env.client,\n        task_queue: \"tq-#{SecureRandom.uuid}\",\n        workflows: [WaitADayWorkflow],\n        workflow_executor: Temporalio::Worker::WorkflowExecutor::ThreadPool.default\n      )\n      worker.run do\n        result = env.client.execute_workflow(\n          WaitADayWorkflow,\n          id: \"wf-#{SecureRandom.uuid}\",\n          task_queue: worker.task_queue\n        )\n        assert_equal 'all done', result\n      end\n    end\n  end\nend\n```\n\nThis test will run almost instantly. This is because by calling `execute_workflow` on our client, we are actually\ncalling `start_workflow` + handle `result`, and `result` automatically skips time as much as it can (basically until the\nend of the workflow or until an activity is run).\n\nTo disable automatic time-skipping while waiting for a workflow result, run code inside a block passed to\n`auto_time_skipping_disabled`.\n\n##### Manual Time Skipping\n\nUntil a workflow is waited on, all time skipping in the time-skipping environment is done manually via\n`WorkflowEnvironment.sleep`.\n\nHere's a workflow that waits for a signal or times out:\n\n```ruby\nrequire 'temporalio/workflow'\n\nclass SignalWorkflow \u003c Temporalio::Workflow::Definition\n  def execute\n    Temporalio::Workflow.timeout(45) do\n      Temporalio::Workflow.wait_condition { @signal_received }\n      'got signal'\n    rescue Timeout::Error\n      'got timeout'\n    end\n  end\n\n  workflow_signal\n  def some_signal\n    @signal_received = true\n  end\nend\n```\n\nTo test a normal signal, you might:\n\n```ruby\nclass MyTest \u003c Minitest::Test\n  def test_signal_workflow_success\n    Temporalio::Testing::WorkflowEnvironment.start_time_skipping do |env|\n      worker = Temporalio::Worker.new(\n        client: env.client,\n        task_queue: \"tq-#{SecureRandom.uuid}\",\n        workflows: [SignalWorkflow],\n        workflow_executor: Temporalio::Worker::WorkflowExecutor::ThreadPool.default\n      )\n      worker.run do\n        handle = env.client.start_workflow(\n          SignalWorkflow,\n          id: \"wf-#{SecureRandom.uuid}\",\n          task_queue: worker.task_queue\n        )\n        handle.signal(SignalWorkflow.some_signal)\n        assert_equal 'got signal', handle.result\n      end\n    end\n  end\nend\n```\n\nBut how would you test the timeout part? Like so:\n\n```ruby\nclass MyTest \u003c Minitest::Test\n  def test_signal_workflow_timeout\n    Temporalio::Testing::WorkflowEnvironment.start_time_skipping do |env|\n      worker = Temporalio::Worker.new(\n        client: env.client,\n        task_queue: \"tq-#{SecureRandom.uuid}\",\n        workflows: [SignalWorkflow],\n        workflow_executor: Temporalio::Worker::WorkflowExecutor::ThreadPool.default\n      )\n      worker.run do\n        handle = env.client.start_workflow(\n          SignalWorkflow,\n          id: \"wf-#{SecureRandom.uuid}\",\n          task_queue: worker.task_queue\n        )\n        env.sleep(50)\n        assert_equal 'got timeout', handle.result\n      end\n    end\n  end\nend\n```\n\nThis test will run almost instantly. The `env.sleep(50)` manually skips 50 seconds of time, allowing the timeout to be\ntriggered without actually waiting the full 45 seconds to time out.\n\n##### Mocking Activities\n\nWhen testing workflows, often you don't want to actually run the activities. Activities are just classes that extend\n`Temporalio::Activity::Definition`. Simply write different/empty/fake/asserting ones and pass those to the worker to\nhave different activities called during the test. You may need to use `activity_name :MyRealActivityClassName` inside\nthe mock activity class to make it appear as the real name.\n\n#### Workflow Replay\n\nGiven a workflow's history, it can be replayed locally to check for things like non-determinism errors. For example,\nassuming the `history_json` parameter below is given a JSON string of history exported from the CLI or web UI, the\nfollowing function will replay it:\n\n```ruby\ndef replay_from_json(history_json)\n  replayer = Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow])\n  replayer.replay_workflow(Temporalio::WorkflowHistory.from_history_json(history_json))\nend\n```\n\nIf there is a non-determinism, this will raise an exception by default.\n\nWorkflow history can be loaded from more than just JSON. It can be fetched individually from a workflow handle, or even\nin a list. For example, the following code will check that all workflow histories for a certain workflow type (i.e.\nworkflow class) are safe with the current workflow code.\n\n```ruby\ndef check_past_histories(client)\n  replayer = Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow])\n  results = replayer.replay_workflows(client.list_workflows(\"WorkflowType = 'MyWorkflow'\").map do |desc|\n    client.workflow_handle(desc.id, run_id: desc.run_id).fetch_history\n  end)\n  results.each { |res| raise res.replay_failure if res.replay_failure }\nend\n```\n\nBut this only raises at the end because by default `replay_workflows` does not raise on failure like `replay_workflow`\ndoes. The `raise_on_replay_failure: true` parameter could be set, or the replay worker can be used to process each one\nlike so:\n\n```ruby\ndef check_past_histories(client)\n  Temporalio::Worker::WorkflowReplayer.new(workflows: [MyWorkflow]) do |worker|\n    client.list_workflows(\"WorkflowType = 'MyWorkflow'\").each do |desc|\n      worker.replay_workflow(client.workflow_handle(desc.id, run_id: desc.run_id).fetch_history)\n    end\n  end\nend\n```\n\nSee the `WorkflowReplayer` API documentation for more details.\n\n#### Advanced Workflow Safety and Escaping\n\nWorkflows use a custom fiber scheduler to make fibers durable. There is also call tracing to prevent accidentally making\nillegal workflow calls. But sometimes in advanced situations, workarounds may be needed. This section describes advanced\nsituations working with the workflow Fiber scheduler and illegal call tracer.\n\n##### Durable Fiber Scheduler\n\nBy default, Temporal considers `Logger`, `sleep`, `Timeout.timeout`, `Queue`, etc illegal. However, there are cases\nwhere it may be desired for these to work locally inside a workflow such as for logging or other side-effecting,\nknown-non-deterministic aspects.\n\nUsers can pass a block to `Temporalio::Workflow::Unsafe.durable_scheduler_disabled` to not use the durable scheduler.\nThis should be used any time the scheduler needs to be bypassed, e.g. for local stdout. Not doing this can cause\nworkflows to get hung in high contention situations. For instance, if there is a logger (that isn't the safe-to-use\n`Temporalio::Workflow.logger`) in a workflow, _technically_ Ruby surrounds the IO writes with a mutex and in extreme\nhigh contention that mutex may durably block and then the workflow task may complete causing hung workflows because no\nevent comes to wake the mutex.\n\nAlso, by default anything that relies on IO wait that is not inside `durable_scheduler_disabled` will fail. It is\nrecommended to put things that need this in `durable_scheduler_disabled`, but if the durable scheduler is still needed\nbut IO wait is also needed, then a block passed to `Temporalio::Workflow::Unsafe.io_enabled` can be used.\n\nNote `durable_scheduler_disabled` implies `illegal_call_tracing_disabled` (see next section). Many use of \n`durable_scheduler_disabled`, such as for tracing or logging, often surround themselves in a\n`unless Temporalio::Workflow.replaying?` block to make sure they don't duplicate the side effects on replay.\n\n##### Illegal Call Tracing\n\nRuby workflow threads employ a `TracePoint` to catch illegal calls such as `sleep` or `Time.now` or `Thread.new`. The\nset of illegal calls can be configured via the `illegal_workflow_calls` parameter when creating a worker. The default\nset is at `Temporalio::Worker.default_illegal_workflow_calls`.\n\nWhen an illegal call is encountered, an exception is thrown. In advanced cases there may be a need to allow an illegal\ncall that is known to be used deterministically. This code can be in a block passed to\n`Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled`. If this has side-effecting behavior that needs to use the\nnon-durable scheduler, use `durable_scheduler_disabled` instead (which implies this, see previous section).\n\n### Activities\n\n#### Activity Definition\n\nActivities can be defined in a few different ways. They are usually classes, but manual definitions are supported too.\n\nHere is a common activity definition:\n\n```ruby\nclass FindUserActivity \u003c Temporalio::Activity::Definition\n  def execute(user_id)\n    User.find(user_id)\n  end\nend\n```\n\nActivities are defined as classes that extend `Temporalio::Activity::Definition` and provide an `execute` method. When\nthis activity is provided to the worker as a _class_ (e.g. `activities: [FindUserActivity]`), it will be instantiated\nfor _every attempt_. Many users may prefer using the same instance across activities, for example:\n\n```ruby\nclass FindUserActivity \u003c Temporalio::Activity\n  def initialize(db)\n    @db = db\n  end\n\n  def execute(user_id)\n    @db[:users].first(id: user_id)\n  end\nend\n```\n\nWhen this is provided to a worker as an instance of the activity (e.g. `activities: [FindUserActivity.new(my_db)]`) then\nthe same instance is reused for each activity.\n\nSome notes about activity definition:\n\n* Temporal activities are identified by their name (or sometimes referred to as \"activity type\"). This defaults to the\n  unqualified class name of the activity, but can be customized by calling the `activity_name` class method.\n* Long running activities should heartbeat regularly, see \"Activity Heartbeating and Cancellation\" later.\n* By default every activity attempt is executed in a thread on a thread pool, but fibers are also supported. See\n  \"Activity Concurrency and Executors\" section later for more details.\n* Technically an activity definition can be created manually via `Temporalio::Activity::Definition::Info.new` that\n  accepts a proc or a block, but the class form is recommended.\n* `activity_dynamic` can be used to mark an activity dynamic. Dynamic activities do not have names and handle any\n  activity that is not otherwise registered. A worker can only have one dynamic activity.\n* `workflow_raw_args` can be used to have activity arguments delivered to `execute` as\n  `Temporalio::Converters::RawValue`s. These are wrappers for the raw payloads that have not been converted to types\n  (but they have been decoded by the codec if present). They can be converted with `payload_converter` on the context.\n* Activities cannot accept keyword arguments.\n\n#### Activity Context\n\nWhen running in an activity, the `Temporalio::Activity::Context` is available via\n`Temporalio::Activity::Context.current` which is backed by a thread/fiber local. In addition to other more advanced\nthings, this context provides:\n\n* `info` - Information about the running activity.\n* `heartbeat` - Method to call to issue an activity heartbeat (see \"Activity Heartbeating and Cancellation\" later).\n* `cancellation` - Instance of `Temporalio::Cancellation` canceled when an activity is canceled (see\n  \"Activity Heartbeating and Cancellation\" later).\n* `worker_shutdown_cancellation` - Instance of `Temporalio::Cancellation` canceled when worker is shutting down (see\n  \"Activity Worker Shutdown\" later).\n* `logger` - Logger that automatically appends a hash with some activity info to every message.\n\n#### Activity Heartbeating and Cancellation\n\nIn order for a non-local activity to be notified of server-side cancellation requests, it must regularly invoke\n`heartbeat` on the `Temporalio::Activity::Context` instance (available via `Temporalio::Activity::Context.current`). It\nis strongly recommended that all but the fastest executing activities call this function regularly.\n\nIn addition to obtaining cancellation information, heartbeats also support detail data that is persisted on the server\nfor retrieval during activity retry. If an activity calls `heartbeat(123)` and then fails and is retried,\n`Temporalio::Activity::Context.current.info.heartbeat_details.first` will be `123`.\n\nAn activity can be canceled for multiple reasons, some server-side and some worker side. Server side cancellation\nreasons include workflow canceling the activity, workflow completing, or activity timing out. On the worker side, the\nactivity can be canceled on worker shutdown (see next section). By default cancellation is relayed two ways - by marking\nthe `cancellation` on `Temporalio::Activity::Context` as canceled, and by issuing a `Thread.raise` or `Fiber.raise` with\nthe `Temporalio::Error::CanceledError`.\n\nThe `raise`-by-default approach was chosen because it is dangerous to the health of the system and the continued use of\nworker slots to require activities opt-in to checking for cancellation by default. But if this behavior is not wanted,\n`activity_cancel_raise false` class method can be called at the top of the activity which will disable the `raise`\nbehavior and just set the `cancellation` as canceled.\n\nIf needing to shield work from being canceled, the `shield` call on the `Temporalio::Cancellation` object can be used\nwith a block for the code to be shielded. The cancellation will not take effect on the cancellation object nor the raise\ncall while the work is shielded (regardless of nested depth). Once the shielding is complete, the cancellation will take\neffect, including `Thread.raise`/`Fiber.raise` if that remains enabled.\n\n#### Activity Worker Shutdown\n\nAn activity can react to a worker shutdown specifically and also a normal cancellation will be sent. A worker will not\ncomplete its shutdown while an activity is in progress.\n\nUpon worker shutdown, the `worker_shutdown_cancellation` cancellation on `Temporalio::Activity::Context` will be\ncanceled. Then the worker will wait a for a grace period set by the `graceful_shutdown_period` worker option (default 0)\nbefore issuing actual cancellation to all still-running activities.\n\nWorker shutdown will then wait on all activities to complete. If a long-running activity does not respect cancellation,\nthe shutdown may never complete.\n\n#### Activity Concurrency and Executors\n\nBy default, activities run in the \"thread pool executor\" (i.e. `Temporalio::Worker::ActivityExecutor::ThreadPool`). This\ndefault is shared across all workers and is a naive thread pool that continually makes threads as needed when none are\nidle/available to handle incoming work. If a thread sits idle long enough, it will be killed.\n\nThe maximum number of concurrent activities a worker will run at a time is configured via its `tuner` option. The\ndefault is `Temporalio::Worker::Tuner.create_fixed` which defaults to 100 activities at a time for that worker. When\nthis value is reached, the worker will stop asking for work from the server until there are slots available again.\n\nIn addition to the thread pool executor, there is also a fiber executor in the default executor set. To use fibers, call\n`activity_executor :fiber` class method at the top of the activity class (the default of this value is `:default` which\nis the thread pool executor). Activities can only choose the fiber executor if the worker has been created and run in a\nfiber, but thread pool executor is always available. Currently due to\n[an issue](https://github.com/temporalio/sdk-ruby/issues/162), workers can only run in a fiber on Ruby versions 3.3 and\nnewer.\n\nTechnically the executor can be customized. The `activity_executors` worker option accepts a hash with the key as the\nsymbol and the value as a `Temporalio::Worker::ActivityExecutor` implementation. Users should usually not need to\ncustomize this. If general code is needed to run around activities, users should use interceptors instead.\n\n#### Activity Testing\n\nUnit testing an activity can be done via the `Temporalio::Testing::ActivityEnvironment` class. Simply instantiate the\nclass, then invoke `run` with the activity to test and the arguments to give. The result will be the activity result or\nit will raise the error raised in the activity.\n\nThe constructor of the environment has multiple keyword arguments that can be set to affect the activity context for the\nactivity.\n\n### Telemetry\n\n#### Metrics\n\nMetrics can be configured on a `Temporalio::Runtime`. Only one runtime is expected to be created for the entire\napplication and it should be created before any clients are created. For example, this configures Prometheus to export\nmetrics at `http://127.0.0.1:9000/metrics`:\n\n```ruby\nrequire 'temporalio/runtime'\n\nTemporalio::Runtime.default = Temporalio::Runtime.new(\n  telemetry: Temporalio::Runtime::TelemetryOptions.new(\n    metrics: Temporalio::Runtime::MetricsOptions.new(\n      prometheus: Temporalio::Runtime::PrometheusMetricsOptions.new(\n        bind_address: '127.0.0.1:9000'\n      )\n    )\n  )\n)\n```\n\nNow every client created will use this runtime. Setting the default will fail if a runtime has already been requested or\na default already set. Technically a runtime can be created without setting the default and be set on each client via\nthe `runtime` parameter, but this is discouraged because a runtime represents a heavy internal engine not meant to be\ncreated multiple times.\n\nOpenTelemetry metrics can be configured instead by passing `Temporalio::Runtime::OpenTelemetryMetricsOptions` as the\n`opentelemetry` parameter to the metrics options. See API documentation for details.\n\n#### OpenTelemetry Tracing\n\nOpenTelemetry tracing for clients, activities, and workflows can be enabled using the\n`Temporalio::Contrib::OpenTelemetry::TracingInterceptor`. Specifically, when creating a client, set the interceptor like\nso:\n\n```ruby\nrequire 'opentelemetry/api'\nrequire 'opentelemetry/sdk'\nrequire 'temporalio/client'\nrequire 'temporalio/contrib/open_telemetry'\n\n# ... assumes my_otel_tracer_provider is a tracer provider created by the user\nmy_tracer = my_otel_tracer_provider.tracer('my-otel-tracer')\n\nmy_client = Temporalio::Client.connect(\n  'localhost:7233', 'my-namespace',\n  interceptors: [Temporalio::Contrib::OpenTelemetry::TracingInterceptor.new(my_tracer)]\n)\n```\n\nNow many high-level client calls and activities/workflows on workers using this client will have spans created on that\nOpenTelemetry tracer.\n\n##### OpenTelemetry Tracing in Workflows\n\nOpenTelemetry works by creating spans as necessary and in some cases serializing them to Temporal headers to be\ndeserialized by workflows/activities to be set on the context. However, OpenTelemetry requires spans to be finished\nwhere they start, so spans cannot be resumed. This is fine for client calls and activity attempts, but Temporal\nworkflows are resumable functions that may start on a different machine than they complete. Due to this, spans created\nby workflows are immediately closed since there is no way for the span to actually span machines. They are also not\ncreated during replay. The spans still become the proper parents of other spans if they are created.\n\nCustom spans can be created inside of workflows using class methods on the\n`Temporalio::Contrib::OpenTelemetry::Workflow` module. For example:\n\n```ruby\nclass MyWorkflow \u003c Temporalio::Workflow::Definition\n  def execute\n    # Sleep for a bit\n    Temporalio::Workflow.sleep(10)\n    # Run activity in span\n    Temporalio::Contrib::OpenTelemetry::Workflow.with_completed_span(\n      'my-span',\n      attributes: { 'my-attr' =\u003e 'some val' }\n    ) do\n      # Execute an activity\n      Temporalio::Workflow.execute_activity(MyActivity, start_to_close_timeout: 10)\n    end\n  end\nend\n```\n\nIf this all executes on one worker (because Temporal has a concept of stickiness that caches instances), the span tree\nmay look like:\n\n```\nStartWorkflow:MyWorkflow          \u003c-- created by client outbound\n  RunWorkflow:MyWorkflow          \u003c-- created inside workflow on first task\n    my-span                       \u003c-- created inside workflow by code\n      StartActivity:MyActivity    \u003c-- created inside workflow when first called\n        RunActivity:MyActivity    \u003c-- created inside activity attempt 1\n    CompleteWorkflow:MyWorkflow   \u003c-- created inside workflow on last task\n```\n\nHowever if, say, the worker crashed during the 10s sleep and the workflow was resumed (i.e. replayed) on another worker,\nthe span tree may look like:\n\n```\nStartWorkflow:MyWorkflow          \u003c-- created by client outbound\n  RunWorkflow:MyWorkflow          \u003c-- created by workflow inbound on first task\n  my-span                         \u003c-- created inside the workflow\n    StartActivity:MyActivity      \u003c-- created by workflow outbound\n      RunActivity:MyActivity      \u003c-- created by activity attempt 1 inbound\n  CompleteWorkflow:MyWorkflow     \u003c-- created by workflow inbound on last task\n```\n\nNotice how the spans are no longer under `RunWorkflow`. This is because spans inside the workflow are not created on\nreplay, so there is no parent on replay. But there are no orphans because we still have the overarching parent of\n`StartWorkflow` that was created by the client and is serialized into Temporal headers so it can always be the parent.\n\nAnd reminder that `StartWorkflow` and `RunActivity` spans do last the length of their calls (so time to start the\nworkflow and time to run the activity attempt respectively), but the other spans have no measurable time because they\nare created in workflows and closed immediately since long-lived spans cannot work for durable software that may resume\non other machines.\n\n### Rails\n\nTemporal Ruby SDK is a generic Ruby library that can work in any Ruby environment. However, there are some common\nconventions for Rails users to be aware of.\n\nSee the [rails_app](https://github.com/temporalio/samples-ruby/tree/main/rails_app) sample for an example of using\nTemporal from Rails.\n\n#### ActiveRecord\n\nFor ActiveRecord, or other general/ORM models that are used for a different purpose, it is not recommended to\ntry to reuse them as Temporal models. Eventually model purposes diverge and models for a Temporal workflows/activities\nshould be specific to their use for clarity and compatibility reasons. Also many Ruby ORMs do many lazy things and\ntherefore provide unclear serialization semantics. Instead, consider having models specific for workflows/activities and\ntranslate to/from existing models as needed. See the [ActiveModel](#activemodel) section on how to do this with\nActiveModel objects.\n\n#### Lazy/Eager Loading\n\nBy default, Rails\n[eagerly loads](https://guides.rubyonrails.org/v7.2/autoloading_and_reloading_constants.html#eager-loading) all\napplication code on application start in production, but lazily loads it in non-production environments. Temporal\nworkflows by default disallow use of IO during the workflow run. With lazy loading enabled in dev/test environments,\nwhen an activity class is referenced in a workflow before it has been explicitly `require`d, it can give an error like:\n\n\u003e Cannot access File path from inside a workflow. If this is known to be safe, the code can be run in a\n\u003e Temporalio::Workflow::Unsafe.illegal_call_tracing_disabled block.\n\nThis comes from `bootsnap` via `zeitwork` because it is lazily loading a class/module at workflow runtime. It is not\ngood to lazily load code during a workflow run because it can be side effecting. Workflows and the classes they\nreference should be eagerly loaded.\n\nTo resolve this, either always eagerly load (e.g. `config.eager_load = true`) or explicitly `require` what is used by a\nworkflow at the top of the file.\n\nNote, this only affects non-production environments.\n\n### Forking\n\nObjects created with the Temporal Ruby SDK cannot be used across forks. This includes runtimes, clients, and workers. By\ndefault, using `Client.connect` uses `Runtime.default` which is lazily created. If it was already created on the parent,\nan exception will occur when trying to reuse it to create clients or workers in a forked child. Similarly any RPC\ninvocation or worker execution inside of a forked child separate from where the runtime or client or worker were created\nwill raise an exception.\n\nIf forking must be used, make sure Temporal objects are only created _inside_ the fork.\n\n### Ractors\n\nIt was an original goal to have workflows actually be Ractors for deterministic state isolation and have the library\nsupport Ractors in general. However, due to the SDK's heavy use of the Google Protobuf library which\n[is not Ractor-safe](https://github.com/protocolbuffers/protobuf/issues/19321), the Temporal Ruby SDK does not currently\nwork with Ractors.\n\n### Platform Support\n\nThis SDK is backed by a Ruby C extension written in Rust leveraging the\n[Temporal Rust Core](https://github.com/temporalio/sdk-core). Gems are currently published for the following platforms:\n\n* `aarch64-linux`\n* `aarch64-linux-musl`\n* `x86_64-linux`\n* `x86_64-linux-musl`\n* `arm64-darwin`\n* `x86_64-darwin`\n\nThis means Linux and macOS for ARM and x64 have published gems.\n\nDue to [an issue](https://github.com/temporalio/sdk-ruby/issues/172) with Windows and multi-threaded Rust, MinGW-based\nWindows (i.e. `x64-mingw-ucrt`) is not supported. But WSL is supported using the normal Linux gem.\n\nDue to [an issue](https://github.com/protocolbuffers/protobuf/issues/16853) with Google Protobuf, latest Linux versions\nof Google Protobuf gems will not work in musl-based environments. Instead use the pure \"ruby\" platform which will build\nthe Google Protobuf gem on install (e.g.\n`gem 'google-protobuf', force_ruby_platform: RUBY_PLATFORM.include?('linux-musl')` in the `Gemfile`).\n\nAt this time a pure source gem is published for documentation reasons, but it cannot be built and will fail if tried.\nBuilding from source requires many files across submodules and requires Rust to be installed. See the [Build](#build)\nsection for how to build a the repository.\n\nThe SDK works on Ruby 3.2+, but due to [an issue](https://github.com/temporalio/sdk-ruby/issues/162), fibers (and\n`async` gem) are only supported on Ruby versions 3.3 and newer.\n\n### Migration from Coinbase Ruby SDK\n\nThe [Coinbase Ruby SDK](https://github.com/coinbase/temporal-ruby) predates this official Temporal SDK and has been a\npopular approach to developing in Temporal with Ruby. While Temporal encourages users to use the official SDK to get new\nfeatures and support, this section covers differences from the Coinbase SDK to help those looking to migrate.\n\nSee [this Ruby sample](https://github.com/temporalio/samples-ruby/tree/main/coinbase_ruby) which demonstrates\ninteroperability between Coinbase Ruby and Temporal Ruby clients, workflows, and activities. Specifically, it discusses\nhow to disable API class loading on the Coinbase Ruby side if needing to use both dependencies in the same project,\nsince two sets of API classes cannot both be present.\n\nMigration cannot be done on a live, running workflow. Overall, Coinbase Ruby workflow events are incompatible with\nTemporal Ruby workflow events at runtime, so both SDK versions cannot have workers for the same task queue. A live\nworkflow migration cannot occur, an separate task queue would be needed. However, Coinbase Ruby clients, workflows, and\nactivities can be used with Temporal Ruby clients, workflows, and activities in either direction. Migrating from the\nCoinbase Ruby SDK to the Temporal Ruby SDK would be similar to migrating from Temporal Go SDK to Temporal Java SDK. You\ncan interact across, but the workflow events are incompatible and therefore the task queues cannot be served by both at\nthe same time.\n\nHere is an overview of the primary differences between the SDKs:\n\n| Feature | Coinbase Ruby | Temporal Ruby |\n| --- | --- | --- |\n| Base module | `Temporal::` | `Temporalio::` |\n| Client + start workflow | Global `Temporal.configure` + `Temporal.start_workflow` | `Temporalio::Client.connect` + `my_client.start_workflow` |\n| Client implementation | Ruby gRPC | Rust gRPC |\n| Activity definition | Extend `Temporal::Activity` + impl `execute` | Extend `Temporalio::Activity::Definition` + impl `execute` |\n| Workflow definition | Extend `Temporal::Workflow` + impl `execute` | Extend `Temporalio::Workflow::Definition` + impl `execute` |\n| Invoke activity from workflow | `MyActivity.execute!` or `workflow.execute_activity!(MyActivity)` | `Workflow.execute_activity(MyActivity)` |\n| Handle signal/query/update in workflow | `workflow.on_signal`/`workflow.on_query`/update-unsupported | Decorate with `workflow_signal`/`workflow_query`/`workflow_update` |\n| Run worker | `Temporal::Worker.new` + `start` | `Temporalio::Worker.new` + `run` |\n\nThis is just a high-level overview, there are many more differences on more specific Temporal components.\n\n## Development\n\n### Build\n\nPrerequisites:\n\n* [Ruby](https://www.ruby-lang.org/) \u003e= 3.2 (i.e. `ruby` and `bundle` on the `PATH`)\n* [Rust](https://www.rust-lang.org/) latest stable (i.e. `cargo` on the `PATH`)\n* This repository, cloned recursively\n* Change to the `temporalio/` directory\n\nFirst, install dependencies:\n\n    # Optional: Change bundler install path to be local\n    bundle config --local path $(pwd)/.bundle\n    bundle install\n\nTo build shared library for development use (ensure you have cloned submodules) :\n\n    bundle exec rake compile\n\n**NOTE**: This will make the current directory usable for the current Ruby version by putting the shared library\n`lib/temporalio/internal/bridge/temporalio_bridge.\u003cext\u003e` in the proper place. But this development shared library may\nnot work for other Ruby versions or other OS/arch combinations. For that, see \"Build Platform-specific Gem\" below.\n\n**NOTE**: This is not `compile:dev` because debug-mode in Rust has\n[an issue](https://github.com/rust-lang/rust/issues/34283) that causes runtime stack size problems.\n\nTo lint, build, and test:\n\n    bundle exec rake\n\n#### Build Platform-specific Gem\n\nThe standard `bundle exec rake build` will produce a gem in the `pkg` directory, but that gem will not be usable because\nthe shared library is not present (neither the Rust code nor the compiled form). To create a platform-specific gem that\ncan be used, `rb-sys-dock` must be run. See the\n[Cross-Compilation documentation](https://oxidize-rb.github.io/rb-sys/tutorial/publishing/cross-compilation.html) in the\n`rb-sys` repository. For example, running:\n\n    bundle exec rb-sys-dock --platform x86_64-linux --ruby-versions 3.2,3.3 --build\n\nWill create a `pkg/temporalio-\u003cversion\u003e-x86_64-linux.gem` file that can be used in x64 Linux environments on both Ruby\n3.2 and Ruby 3.3 because it contains the shared libraries. For this specific example, the shared libraries are inside\nthe gem at `lib/temporalio/internal/bridge/3.2/temporalio_bridge.so` and\n`lib/temporalio/internal/bridge/3.3/temporalio_bridge.so`.\n\n### Testing\n\nNote you can set `TEMPORAL_TEST_CLIENT_TARGET_HOST` and `TEMPORAL_TEST_CLIENT_TARGET_NAMESPACE`\n(optional, defaults to 'default') environment variables to use an existing server.\n\nThis project uses `minitest`. To test:\n\n    bundle exec rake test\n\nCan add options via `TESTOPTS`. E.g. single test:\n\n    bundle exec rake test TESTOPTS=\"--name=test_some_method\"\n\nE.g. all starting with prefix:\n\n    bundle exec rake test TESTOPTS=\"--name=/^test_some_method_prefix/\"\n\nE.g. all for a class:\n\n    bundle exec rake test TESTOPTS=\"--name=/SomeClassName/\"\n\nE.g. show all test names while executing:\n\n    bundle exec rake test TESTOPTS=\"--verbose\"\n\n### Code Formatting and Type Checking\n\nThis project uses `rubocop`:\n\n    bundle exec rake rubocop:autocorrect\n\nThis project uses `steep`. First may need the RBS collection:\n\n    bundle exec rake rbs:install_collection\n\nNow can run `steep`:\n\n    bundle exec rake steep\n\n### Proto Generation\n\nRun:\n\n    bundle exec rake proto:generate\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftemporalio%2Fsdk-ruby","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftemporalio%2Fsdk-ruby","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftemporalio%2Fsdk-ruby/lists"}