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 install
Alternatively:
gem install hati_operation
HatiOperation 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
end
The 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
end
Prefer 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
end
class 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
end
Run the test-suite with:
bundle exec rspec
HatiOperation 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.