{"id":28125084,"url":"https://github.com/incorvia/yantra","last_synced_at":"2026-03-15T18:34:13.327Z","repository":{"id":287995971,"uuid":"966487909","full_name":"incorvia/yantra","owner":"incorvia","description":"Yantra is a robust, DAG-based workflow orchestration engine for Ruby applications, designed with flexibility, maintainability, and observability in mind. It allows you to define complex workflows with dependent steps and execute them reliably using common background worker systems.","archived":false,"fork":false,"pushed_at":"2025-04-30T02:43:26.000Z","size":512,"stargazers_count":2,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-30T03:31:51.951Z","etag":null,"topics":["dag","durable","ruby-gem","workflows"],"latest_commit_sha":null,"homepage":"","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/incorvia.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-15T02:10:25.000Z","updated_at":"2025-04-30T02:40:41.000Z","dependencies_parsed_at":null,"dependency_job_id":"353ea5d0-2268-4bd3-94e0-281a4c1a2c4f","html_url":"https://github.com/incorvia/yantra","commit_stats":null,"previous_names":["incorvia/yantra"],"tags_count":22,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incorvia%2Fyantra","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incorvia%2Fyantra/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incorvia%2Fyantra/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/incorvia%2Fyantra/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/incorvia","download_url":"https://codeload.github.com/incorvia/yantra/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254110399,"owners_count":22016392,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["dag","durable","ruby-gem","workflows"],"created_at":"2025-05-14T09:20:06.100Z","updated_at":"2026-03-15T18:34:13.316Z","avatar_url":"https://github.com/incorvia.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Yantra\n\n[![Gem Version](https://badge.fury.io/rb/yantra.svg)](https://badge.fury.io/rb/yantra)\n[![Build Status](https://github.com/incorvia/yantra/actions/workflows/ci.yml/badge.svg)](https://github.com/incorvia/yantra/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n\nYantra is a robust, backend-agnostic workflow engine for Ruby applications. Define complex workflows as Directed Acyclic Graphs (DAGs) of individual steps (jobs), run them reliably via background workers, and gain insight through a built-in event system.\n\nYantra focuses on:\n\n* **Reliability:** Clear state management and robust error handling with configurable retries.\n* **Flexibility:** Choose your own backend for persistence (e.g., ActiveRecord, Redis\\*) and background job processing (e.g., Active Job, Sidekiq\\*, Resque\\*) via adapters. *(Adapters marked with \\* are planned but may not be implemented yet).*\n* **Observability:** Emit events at key lifecycle points for monitoring and tracing.\n* **Maintainability:** A clean, modern codebase with **zero external runtime dependencies** in its core.\n\n## Features\n\n* Define complex workflows with dependencies between steps.\n* Backend-agnostic persistence via a Repository pattern (ActiveRecord adapter included).\n* Background job processing via adapters (Active Job adapter included).\n* Configurable automatic retries for failed steps.\n* **Delayed step execution:** Schedule steps to run after a specified delay.\n* Workflow and step cancellation.\n* Simple data pipelining between dependent steps.\n* Event-driven notifications for observability (adapters for Null, Logger, ActiveSupport::Notifications included).\n* Zero external runtime gem dependencies required for the core library.\n\n## Architecture Overview\n\nYantra employs a layered architecture to separate concerns (core logic, persistence, background job queuing) and allow for flexibility through adapters.\n\n```text\n+-------------------+     +---------------------+     +--------------------+\n| User Application  | ---\u003e| Yantra::Client      | ---\u003e| Yantra Core Logic  |\n| (Defines Wf/Jobs) |     | (Public API)        |     | (Orchestration, State) |\n+-------------------+     +---------------------+     +--------------------+\n                                                      |\n                                                      | Uses Interfaces \u0026 Services\n                                                      V\n+---------------------------------+  +---------------------------------+\n| Yantra::Core::DependentProcessor|  | Yantra::Core::StepEnqueuer      |\n| (Handles Success/Failure Logic) |  | (Handles Job System Handoff)    |\n+---------------------------------+  +---------------------------------+\n              |                                      |\n              V Uses                                 V Uses\n+---------------------------------+  +---------------------------------+\n| Yantra::Core::StateTransitionSvc|  | Yantra::Persistence::Repo       |\n| (Ensures Valid State Changes)   |  | (Interface)                     |\n+---------------------------------+  +---------------------------------+\n                                     | Implemented By\n                                     V\n+----------------------------------+ +----------------------------+\n| ActiveRecord Adapter | Redis Adpt*| | ActiveJob Adapter | Sidekiq Adpt*| ...\n+----------------------------------+ +----------------------------+\n  | Uses                           | Uses\n  V                              V\n+----------+ +-------+           +-----------+ +---------+\n| Postgres | | Redis |           | ActiveJob | | Sidekiq | ...\n+----------+ +-------+           +-----------+ +---------+\n```\n\n**Recent Architectural Refinements:**\n\n* **Service Objects:** Core logic for handling step completion outcomes (`DependentProcessor`) and ensuring valid state transitions (`StateTransitionService`) has been extracted into dedicated service objects. This improves separation of concerns and testability within the core engine.\n* **State Machine:** The state lifecycle has been refined. The `ENQUEUED` state has been removed. Steps now transition from `PENDING` -\u003e `AWAITING_EXECUTION` (during the attempt to hand off to the job backend) -\u003e `RUNNING` (when the worker picks it up). The `enqueued_at` timestamp on the step record indicates successful handoff to the job backend. `POST_PROCESSING` is used internally after successful `perform` before dependents are processed.\n* **Repository Interface:** The persistence interface (`RepositoryInterface`) has been refactored for better consistency (see notes below).\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'yantra'\n```\n\nAnd then execute:\n\n```bash\n$ bundle install\n```\n\n**Adapter Dependencies:**\n\nYantra core has no runtime dependencies. However, you will need to add gems for the specific adapters you choose to use:\n\n* For `persistence_adapter: :active_record`: Add `gem 'activerecord'` (and likely `gem 'pg'` or `gem 'sqlite3'`).\n* For `worker_adapter: :active_job`: Add `gem 'activejob'` (and likely `gem 'activesupport'`).\n* For `notification_adapter: :active_support_notifications`: Add `gem 'activesupport'`.\n* *(Other adapters like Redis, Sidekiq, etc., will require their respective gems when implemented).*\n\n## Configuration\n\nCreate an initializer file (e.g., `config/initializers/yantra.rb`) to configure Yantra:\n\n```ruby\n# config/initializers/yantra.rb\n\nYantra.configure do |config|\n  # --- Persistence Adapter ---\n  # Choose where workflow and step state is stored.\n  # Built-in: :active_record\n  # Custom: Pass an object/class that implements Yantra::Persistence::RepositoryInterface\n  config.persistence_adapter = :active_record\n  # Optional hash passed to the adapter's initializer (if it accepts one)\n  # config.persistence_options = {}\n\n  # --- Worker Adapter ---\n  # Choose how background jobs are enqueued.\n  # Built-in: :active_job\n  # Custom: Pass an object/class that implements Yantra::Worker::EnqueuingInterface\n  config.worker_adapter = :active_job\n  # Optional hash passed to the adapter's initializer\n  # config.worker_adapter_options = {}\n\n  # --- Notification Adapter ---\n  # Choose how lifecycle events are published.\n  # Built-in: :null (default), :logger, :active_support_notifications\n  # Custom: Pass an object/class that implements Yantra::Events::NotifierInterface\n  config.notification_adapter = :logger # Example: Log events\n  # Optional hash passed to the adapter's initializer\n  # config.notification_adapter_options = {}\n\n  # --- Logger ---\n  # Configure the logger instance Yantra uses. Defaults to Rails.logger or STDOUT logger.\n  # config.logger = MyCustomLogger.new\n\n  # --- Default Step Options ---\n  # Configure default retry attempts for steps.\n  # `retries: 3` means a total of 4 attempts (initial + 3 retries).\n  # Default is 0 retries (1 attempt) if not set.\n  config.default_step_options[:retries] = 3\n  # You can also set default_max_step_attempts directly (overrides retries calculation if present)\n  # config.default_max_step_attempts = 4\nend\n```\n\n**Adapter Loading:**\n\n* **Built-in Adapters:** When you configure a built-in adapter using its symbol (e.g., `:active_record`, `:active_job`, `:logger`), Yantra automatically requires the necessary internal file. You just need to ensure the underlying gem (like `activerecord`) is in your `Gemfile`.\n* **Custom Adapters:** If you build your own adapter class (e.g., `MyCustomNotifier`), you need to:\n    1.  Ensure the file defining your class is loaded *before* Yantra is configured (e.g., using `require 'path/to/my_custom_notifier'` in your initializer).\n    2.  Configure Yantra using either the class constant (`config.notification_adapter = MyCustomNotifier`) or a symbol (`config.notification_adapter = :my_custom_notifier`, assuming your class is defined as `Yantra::Events::MyCustomNotifier::Adapter`).\n\n## Database Setup (ActiveRecord)\n\nIf you are using the `:active_record` persistence adapter, you need to generate and run the database migrations:\n\n1.  **Generate Initial Migrations:**\n    ```bash\n    $ rails g yantra:install\n    ```\n    This will copy the necessary migration files (`create_yantra_workflows`, `create_yantra_steps`, `create_yantra_step_dependencies`) into your application's `db/migrate/` directory.\n2.  **Run Migrations:**\n    ```bash\n    $ rails db:migrate\n    ```\n\n## Usage\n\n### Defining a Workflow\n\nWorkflows orchestrate steps. Define a workflow by inheriting from `Yantra::Workflow` and implementing a `perform` method. Use the `run` DSL method inside `perform` to define the steps and their dependencies.\n\n```ruby\n# app/workflows/order_processing_workflow.rb\nrequire 'active_support/core_ext/numeric/time' # For 5.minutes etc.\n\nclass OrderProcessingWorkflow \u003c Yantra::Workflow\n  def perform(order_id:, user_id:)\n    # Define steps using the `run` DSL\n    # run StepClass, name: :symbolic_name (optional), params: {..}, after: [dependency_ref, ...], delay: duration\n\n    charge_step = run ChargeCreditCardStep, name: :charge, params: { order_id: order_id, amount: 100.00 }\n\n    # This step runs immediately after charge_step succeeds\n    update_step = run UpdateInventoryStep, name: :inventory, params: { order_id: order_id }, after: charge_step\n\n    # This step runs 5 minutes after charge_step succeeds\n    email_step = run SendConfirmationEmailStep, name: :email, params: { order_id: order_id, user_id: user_id }, after: charge_step, delay: 5.minutes\n\n    # This step runs after both inventory and email steps succeed (email might be delayed)\n    run ArchiveOrderStep, name: :archive, params: { order_id: order_id }, after: [update_step, email_step]\n  end\nend\n```\n\n* `run StepClass`: Specifies the `Yantra::Step` subclass to execute.\n* `name:`: An optional symbolic name for the step within the workflow definition (used for dependencies). If omitted, a default name is generated.\n* `params:`: A hash of arguments passed to the step's `perform` method. Must be JSON-serializable.\n* `after:`: Specifies dependencies. Can be a single step reference variable (like `charge_step`) or an array of references (`[update_step, email_step]`). A step only runs after all its dependencies have successfully completed.\n* `delay:`: **(New)** An optional delay before the step is enqueued *after* its dependencies are met. Accepts a non-negative **Numeric value representing seconds** (integer or float). If `activesupport` is loaded in your application, you can also conveniently use `ActiveSupport::Duration` objects (e.g., `10.seconds`, `1.hour`). The underlying job backend handles the scheduled execution.\n\n### Defining a Step\n\nSteps represent individual units of work. Define a step by inheriting from `Yantra::Step` and implementing a `perform` method.\n\n```ruby\n# app/steps/charge_credit_card_step.rb\nclass ChargeCreditCardStep \u003c Yantra::Step\n  # Optional: Override default retries just for this step\n  # def self.yantra_max_attempts; 5; end # Total 5 attempts\n\n  def perform(order_id:, amount:)\n    Yantra.logger.info {\"Attempting to charge order #{order_id} for #{amount}\"}\n    payment_service = PaymentGateway.new\n    result = payment_service.charge(order_id: order_id, amount: amount)\n\n    unless result.success?\n      # Raise an error to mark the step as failed (will trigger retries if configured)\n      raise StandardError, \"Payment failed: #{result.error_message}\"\n    end\n\n    # Return value is stored as step output (must be JSON-serializable)\n    { transaction_id: result.transaction_id, charged_amount: amount }\n  end\nend\n```\n\n* The `perform` method receives arguments defined in the workflow's `run` call (`params:`). Use keyword arguments.\n* Raise an exception within `perform` to indicate failure. Yantra will catch it, record the error, and handle retries based on configuration.\n* The return value of `perform` is saved as the step's output (must be JSON-serializable).\n\n### Creating \u0026 Starting a Workflow\n\nUse the `Yantra::Client` to interact with workflows.\n\n```ruby\n# Create the workflow instance and persist its structure\nworkflow_id = Yantra::Client.create_workflow(OrderProcessingWorkflow, order_id: 123, user_id: 456)\n\n# Start the workflow (enqueues initial steps)\nYantra::Client.start_workflow(workflow_id)\n```\n\n### Child Workflows\n\nYantra supports parent-child workflow relationships with idempotency, enabling hierarchical workflow structures for batch processing scenarios.\n\n```ruby\n# Create a parent workflow\nparent_id = Yantra::Client.create_workflow(ProcessBatchWorkflow, batch_id: 123)\n\n# Create a child workflow with idempotency\nchild_id = Yantra::Client.create_workflow(\n  ProcessIndividualItemWorkflow,\n  parent_workflow_id: parent_id,\n  idempotency_key: \"item-456-ABC123\",\n  item_id: 456,\n  sku: \"ABC123\"\n)\n\n# Find all child workflows for a parent\nchild_workflows = Yantra::Client.find_child_workflows(parent_id)\n\n# Check for existing idempotency keys (globally unique)\nexisting_keys = Yantra::Client.find_existing_idempotency_keys([\"key1\", \"key2\"])\n```\n\n**Key Benefits:**\n- **True Replayability:** Parent workflows can be retried safely without creating duplicate children\n- **Enhanced Observability:** Track relationships between parent and child workflows\n- **Atomic Granularity:** Each child workflow can have its own complex, multi-step process\n- **Efficient Processing:** Check for existing workflows in a single database query\n\nFor more details, see the [Child Workflows documentation](docs/child_workflows.md).\n\n### Data Pipelining\n\nA step can access the output of its direct parent(s) using the `parent_outputs` helper method within its `perform` method.\n\n```ruby\n# app/steps/send_confirmation_email_step.rb\nclass SendConfirmationEmailStep \u003c Yantra::Step\n  def perform(order_id:, user_id:)\n    # parent_outputs returns a hash: { parent_step_id =\u003e parent_output_hash }\n    # Note: Output hash keys might be strings if deserialized from JSON via AR/SQLite\n    charge_step_output = parent_outputs.values.first # Assuming only one parent\n\n    transaction_id = charge_step_output['transaction_id'] if charge_step_output\n    amount = charge_step_output['charged_amount'] if charge_step_output\n\n    unless transaction_id \u0026\u0026 amount\n      raise \"Could not find required charge details in parent output: #{parent_outputs.inspect}\"\n    end\n\n    Yantra.logger.info {\"Sending confirmation for order #{order_id}, transaction #{transaction_id}\"}\n    Mailer.send_confirmation(user_id: user_id, order_id: order_id, transaction_id: transaction_id, amount: amount)\n\n    { email_sent: true }\n  end\nend\n```\n\n* `parent_outputs`: Returns a hash where keys are the IDs of direct parent steps and values are their corresponding output hashes (deserialized from JSON).\n* Be aware that hash keys within the output might be strings if using ActiveRecord with certain database backends (like SQLite) unless specific model serialization (`serialize :output, JSON`) is used. Access them accordingly (e.g., `output['key']`).\n\n### Monitoring and Management\n\nUse the `Yantra::Client` API or query the persistence layer directly (if using ActiveRecord) to monitor workflows.\n\n```ruby\n# Find workflow status\nworkflow = Yantra::Client.find_workflow(workflow_id)\nputs \"Workflow state: #{workflow\u0026.state}\"\nputs \"Workflow has failures: #{workflow\u0026.has_failures}\"\n\n# Find a specific step\nstep = Yantra::Client.find_step(step_id)\nputs \"Step state: #{step\u0026.state}\" # e.g., pending, awaiting_execution, running, succeeded, failed, cancelled\n# Access output/error (may be string or hash depending on persistence/serialization)\nputs \"Step output: #{step\u0026.output.inspect}\"\nputs \"Step error: #{step\u0026.error.inspect}\"\n# Access delay info\nputs \"Step Delay Specified (seconds): #{step\u0026.delay_seconds}\"\nputs \"Step Enqueued At (Timestamp): #{step\u0026.enqueued_at}\" # Timestamp when successfully handed off\n\n# Get all steps for a workflow\nsteps = Yantra::Client.list_steps(workflow_id: workflow_id)\n\n# Get steps with a specific status\nfailed_steps = Yantra::Client.list_steps(workflow_id: workflow_id, status: :failed)\n# Find steps waiting for worker/timer (successfully handed off)\nscheduled_steps = Yantra::Client.list_steps(workflow_id: workflow_id, status: :awaiting_execution).select { |s| s.enqueued_at.present? }\n# Find steps stuck during awaiting_execution (handoff failed)\n# These are steps in awaiting_execution that were not successfully handed off to the job system\n# (e.g. due to transient enqueue failure, like Redis outage)\nstuck_steps = Yantra::Client.list_steps(workflow_id: workflow_id, status: :awaiting_execution).select { |s| s.enqueued_at.nil? }\n\n# Cancel a running or pending workflow\nYantra::Client.cancel_workflow(workflow_id)\n\n# Retry all failed steps in a failed workflow\n# (Resets workflow state to running, re-enqueues FAILED steps and steps stuck in AWAITING_EXECUTION)\nYantra::Client.retry_failed_steps(workflow_id)\n```\n\n## Events / Observability\n\nYantra publishes events at key lifecycle points using the configured `notification_adapter`.\n\n**Key Events:**\n\n* `yantra.workflow.started`\n* `yantra.workflow.succeeded`\n* `yantra.workflow.failed`\n* `yantra.workflow.cancelled`\n* `yantra.step.bulk_enqueued` (Published when steps are successfully handed off to the job backend, includes immediately processed and delayed steps)\n* `yantra.step.started`\n* `yantra.step.succeeded`\n* `yantra.step.failed` (Published on permanent failure after retries or critical error)\n* `yantra.step.cancelled`\n\n**Payloads:** Event payloads are hashes containing relevant IDs, class names, state, timestamps, and potentially output or error information. The `yantra.step.bulk_enqueued` payload includes an `enqueued_ids` array containing IDs for steps successfully processed in that batch.\n\n**Subscribing (Example using ActiveSupport::Notifications):**\n\nIf you configure `config.notification_adapter = :active_support_notifications`, you can subscribe using standard `ActiveSupport::Notifications` methods:\n\n```ruby\n# config/initializers/yantra_events.rb\n\nActiveSupport::Notifications.subscribe(/yantra\\..*/) do |name, start, finish, id, payload|\n  Rails.logger.info \"[YANTRA EVENT] #{name} | Duration: #{finish - start}s | Payload: #{payload.inspect}\"\n  # Send to monitoring, trigger alerts, etc.\n  case name\n  when 'yantra.workflow.failed'\n    MonitoringService.alert(\"Workflow Failed: #{payload[:workflow_id]}\")\n  when 'yantra.step.failed'\n    MonitoringService.track_step_failure(payload)\n  end\nend\n```\n\n## Error Handling and Retries\n\nYantra provides configurable automatic retries for steps that fail during execution. It employs a cooperative strategy with the underlying job runner (configured via `worker_adapter`) to handle the retry process.\n\n**Division of Responsibility:**\n\n* **Yantra (`RetryHandler`)**: Determines *if* a step should be retried based on its configured number of attempts (`default_step_options[:retries]` or step-specific `yantra_max_attempts`) and the current execution count. It manages Yantra's internal state related to retries.\n* **Job Runner Backend (Sidekiq, ActiveJob Adapter, etc.)**: Handles the *mechanism* of retrying a job instance, including awaiting_execution the next attempt using its built-in delay/backoff algorithms. Yantra leverages this backend capability.\n\n**Signaling Mechanism:**\n\nYantra signals its decision to the job runner backend as follows:\n\n1.  **Retry Needed:** If a step fails but Yantra determines it has retries remaining, the `StepExecutor` will **raise the original error**. The job runner backend catches this error and triggers its own retry process.\n2.  **Stop Processing (Success or Terminal Failure):** If a step succeeds, OR if it fails but has reached its maximum retry attempts according to Yantra, the `StepExecutor` will **complete normally without raising an error**. The job runner backend sees this as a successful job completion and will *not* attempt further retries.\n\n**Backend Retry Configuration (Handled by Yantra Defaults):**\n\nFor this cooperative system to work correctly, the underlying job runner must execute Yantra's internal StepJob classes with **retries enabled** and a **sufficient attempt limit**.\n\n**Yantra provides sensible defaults for this out-of-the-box:**\n\n* **Internal Defaults:** Yantra's built-in adapter job classes (`Yantra::Worker::ActiveJob::StepJob` and `Yantra::Worker::Sidekiq::StepJob` [if available]) include internal configurations that enable backend retries with a default limit (typically **25 attempts**, aligning with Sidekiq's standard). This uses ActiveJob's `retry_on` or Sidekiq's `sidekiq_options` internally within the adapter code.\n* **User Configuration:** In most scenarios, **no additional configuration is required** by the user specifically for Yantra's retry mechanism. The built-in defaults ensure the backend retry functionality is available for Yantra to leverage.\n\n**Important Considerations:**\n\n* **DO NOT Disable Backend Retries:** It is crucial that you **do not** globally disable retries in your job backend or specifically configure Yantra's job classes (e.g., via global ActiveJob settings or monkey-patching) with `retry: false`, `attempts: 0`, or equivalent. Disabling backend retries will prevent Yantra's retry logic from functioning as intended.\n* **Sufficient Attempts:** The default backend attempt limit provided by Yantra's adapters (e.g., 25) is designed to be sufficient for most use cases. If you configure a specific Yantra step to require *more* retries than this backend limit (e.g., `retries: 30` in Yantra), the backend runner would stop retrying prematurely. This is generally an edge case; exceptionally high retry counts might warrant rethinking the step's design.\n* **Potential Overrides (ActiveJob):** If using the `:active_job` adapter, be aware that global settings in your `ApplicationJob` (from which Yantra's `ActiveJob::StepJob` likely inherits) could potentially influence retry behavior. However, the specific `retry_on` configuration within Yantra's `StepJob` should generally take precedence for the errors it covers.\n* **Customizing Attempts (Advanced):** If you have a specific need to change the number of backend retry attempts for Yantra jobs (e.g., increase it beyond the default 25), you may need to investigate advanced techniques like subclassing Yantra's adapter job or checking if Yantra offers configuration overrides via `config.worker_adapter_options`.\n\n**In summary:** Yantra is designed to work with standard backend retry mechanisms enabled, and it includes internal defaults to facilitate this. Ensure you don't disable backend retries, and Yantra's retry system should work as expected.\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/incorvia/yantra. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](link/to/your/CODE_OF_CONDUCT.md).\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n\n## Code of Conduct\n\nEveryone interacting in the Yantra project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](link/to/your/CODE_OF_CONDUCT.md).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fincorvia%2Fyantra","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fincorvia%2Fyantra","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fincorvia%2Fyantra/lists"}