HatiOperation is a next-generation Ruby toolkit that combines powerful service orchestration with modern AI-ready architecture. Built on top of hati-command, it serves as both a traditional service aggregator and an AI-enhanced orchestrator, making it perfect for building modern applications that blend business logic with AI capabilities.
- Step-based execution – write each unit of work as a small service object and compose them with
step - Implicit result propagation – methods return
Success(...)orFailure(...)and are automatically unpacked - Fail-fast transactions – stop the chain as soon as a step fails
- Dependency injection (DI) – override steps at call-time for ultimate flexibility
- Macro DSL – declaratively configure validation, error mapping, transactions and more
- Service aggregation – orchestrate multiple services into cohesive business operations
- Tool Integration – seamlessly integrate AI services and LLM tools
- Safety Boundaries – built-in guardrails for AI operations
- Action Composition – chain multiple AI actions safely
- State Management – track and manage AI agent state
- Structured Patterns – clear patterns for both human and AI comprehension
- Predictable Flow – consistent operation structure for better maintainability
- Self-Documenting – clear step definitions aid both human and AI understanding
- Context Awareness – easy access to operation context for all services
- Key Features
- Architecture
- Installation
- Quick Start
- Step DSL
- Dependency Injection
- Alternative DSL Styles
- Testing
- Authors
- Development
- Contributing
- License
- Code of Conduct
HatiOperation builds on top of hati-command and implements a versatile architecture that supports both traditional service aggregation and AI-enhanced operations:
┌─────────────────────────────────────────────────────────────┐ │ HatiOperation │ │ (Universal Service Orchestrator) │ ├─────────────────────────────────────────────────────────────┤ │ │ │ Traditional Services AI/ML Services │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Business │ │ LLM │ │ │ │ Logic │ │ Tools │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ Data │ │ Agent │ │ │ │ Services │ │ Actions │ │ │ └─────────────┘ └─────────────┘ │ │ │ │ ┌─────────────┐ ┌─────────────┐ │ │ │ External │ │ Safety │ │ │ │ APIs │ │ Guards │ │ │ └─────────────┘ └─────────────┘ │ │ │ ├─────────────────────────────────────────────────────────────┤ │ hati-command │ │ (Foundation Layer) │ └─────────────────────────────────────────────────────────────┘ This dual-purpose architecture allows you to:
- Compose traditional business services with robust error handling and transactions
- Integrate AI capabilities with built-in safety mechanisms
- Mix and match both paradigms in the same operation
- Maintain clean separation of concerns while sharing common infrastructure
Add HatiOperation to your Gemfile and bundle:
# Gemfile gem 'hati_operation'bundle installAlternatively:
gem install hati_operationHatiOperation can be used for both traditional business operations and AI-enhanced services. Here are examples of both:
# app/controllers/api/v1/withdrawal_controller.rb class Api::V1::WithdrawalController < ApplicationController def create result = Withdrawal::Operation::Create.call(params: params.to_unsafe_h) run_and_render(result) end private def run_and_render(result) if result.success? render json: TransferSerializer.new.serialize(result.value), status: :created else error = ApiError.new(result.value) render json: error.to_json, status: error.status end end end # app/operations/withdrawal/operation/create.rb class Withdrawal::Operation::Create < HatiOperation::Base # Wrap everything in DB transaction ar_transaction :funds_transfer_transaction! def call(params:) params = step MyApiContract.call(params), err: ApiErr.call(422) transfer = step funds_transfer_transaction(params[:account_id]) EventBroadcast.new.stream(transfer.to_event) transfer.meta end def funds_transfer_transaction(acc_id) acc = Account.find_by(find_by: acc_id).presence : Failure!(err: ApiErr.call(404)) withdrawal = step WithdrawalService.call(acc), err: ApiErr.call(409) transfer = step ProcessTransferService.call(withdrawal), err: ApiErr.call(503) Success(transfer) end end# app/operations/ai/content_generation.rb class AI::Operation::ContentGeneration < HatiOperation::Base # Register safety boundaries safety_guard :content_filter rate_limit max_tokens: 1000 step validator: ContentValidator step generator: LLMService step filter: ContentFilter step formatter: OutputFormatter def call(params:) # Validate input and prepare prompt input = step validator.call(params[:prompt]) # Generate content with safety checks content = step generator.call(input), err: AIErr.call(503) filtered = step filter.call(content), err: AIErr.call(422) # Format and return step formatter.call(filtered) end end # Usage in controller class Api::V1::ContentController < ApplicationController def create result = AI::Operation::ContentGeneration.call(params: params.to_unsafe_h) do # Override services for different models/providers step generator: OpenAIService step filter: CustomContentFilter end render_result(result) end end# Common configuration for API operations class ApiOperation < HatiOperation::Base operation do unexpected_err ApiErr.call(500) end end # Common configuration for AI operations class AIOperation < HatiOperation::Base operation do unexpected_err AIErr.call(500) safety_guard :content_filter rate_limit true end endThe DSL gives you fine-grained control over every stage of the operation:
step– register a dependency serviceparams– validate/transform incoming parameterson_success– handle successful operation resultson_failure– map and handle failure results
See: hati-command for all configuration options
ar_transaction– execute inside database transactionfail_fast– configure fail-fast behaviorfailure– set default failure handlingunexpected_err– configure generic error behavior
At runtime you can swap out any step for testing, feature-flags, or different environments:
result = Withdrawal::Operation::Create.call(params) do step broadcast: DummyBroadcastService step transfer: StubbedPaymentProcessor endPrefer more declarative code? Use the class-level DSL:
class Withdrawal::Operation::Create < ApiOperation params CreateContract, err: ApiErr.call(422) ar_transaction :funds_transfer_transaction! step withdrawal: WithdrawalService, err: ApiErr.call(409) step transfer: ProcessTransferService, err: ApiErr.call(503) step broadcast: Broadcast on_success SerializerService.call(Transfer, status: 201) on_failure ApiErrorSerializer # requires :params keyword to access overwritten params # same as params = step CreateContract.call(params), err: ApiErr.call(422) def call(params:) transfer = step funds_transfer_transaction!(params[:account_id]) broadcast.new.stream(transfer.to_event) transfer.meta end def funds_transfer_transaction!(acc_id) acc = step(err: ApiErr.call(404)) { User.find(id) } withdrawal = step withdrawal.call(acc) transfer = step transfer.call(withdrawal) Success(transfer) end end class Api::V2::WithdrawalController < ApiController def create run_and_render Withdrawal::Operation::Create end private def run_and_render(operation, &block) render JsonResult.prepare operation.call(params.to_unsafe_h).value end endclass Api::V2::WithdrawalController < ApplicationController def create run_and_render Withdrawal::Operation::Create.call(params.to_unsafe_h) do step broadcast: API::V2::BroadcastService step transfer: API::V2::PaymentProcessorService step serializer: ExtendedTransferSerializer end end endRun the test-suite with:
bundle exec rspecHatiOperation is fully covered by RSpec. See spec/ for reference examples including stubbed services and DI.
After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.
To 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 created tag, and push the .gem file to rubygems.org.
Bug reports and pull requests are welcome on GitHub at https://github.com/hackico-ai/hati-command. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the HatCommand project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.