This file is managed by Rizome CLI. Do not edit directly. Update RIZOME.md and run 'rizome sync' instead.
Rails is a Python library for production-grade lifecycle management of AI agents. It provides conditional message injection, workflow execution, and composable lifecycle functions triggered by custom conditions.
# Install with PDM (preferred)
pdm install # Install core dependencies
pdm install --dev # Install with development dependencies
pdm venv create # Create virtual environment if needed
# Alternative: pip
pip install -e . # Editable install
pip install -e ".[dev]" # With dev dependencies
pip install -e ".[adapters]" # With framework adapters# Run all tests
pdm run test
# Run tests with coverage
pdm run test-cov
# Run specific test file
pdm run pytest tests/test_rails.py -v
# Run specific test function
pdm run pytest tests/test_rails.py::TestRails::test_basic_message_injection -v
# Run tests with specific marker or pattern
pdm run pytest -k "injection" -v# Linting (using ruff)
pdm run lint
# Formatting (using black)
pdm run format # Apply formatting
pdm run format-check # Check formatting without changes
# Type checking (using mypy)
pdm run typecheck
# Clean build artifacts
pdm run cleanpdm run build # Build wheel and sdist
pdm run check # Verify built packages with twineRails (src/rails/core.py)
- Central orchestrator managing conditional rules and lifecycle
- Maintains global registry for
current_rails()access - Handles both injection rules and execution rules
- Context manager for automatic lifecycle management
Store (src/rails/store.py)
- Thread-safe state management with counters and arbitrary values
- Provides both sync and async interfaces
- Supports bulk operations and existence checks
Conditions (src/rails/conditions.py)
- Base
Conditionprotocol with implementations:LambdaCondition: Custom logic via lambda functionsCounterCondition: Numeric comparisons on countersStateCondition: Value comparisons on state- Logical operators:
AndCondition,OrCondition,NotCondition
Injectors (src/rails/injectors.py)
- Message manipulation strategies:
AppendInjector: Add to endPrependInjector: Add to startInsertInjector: Insert at indexReplaceInjector: Replace messagesConditionalInjector: Apply based on conditions
Lifecycle (src/rails/lifecycle.py)
@lifecycle_functiondecorator for composable lifecycle componentsLifecycleRegistryfor managing registered functionsLifecycleManagerfor orchestrating setup/teardown- Built-in lifecycle functions for common patterns
Execution (src/rails/execution.py)
BackgroundExecutorfor async workflow executionWorkflowOrchestratorfor complex execution patterns- Support for parallel, sequential, and conditional pipelines
- Global executor management with proper cleanup
Adapters (src/rails/adapters/)
BaseAdapter: Abstract base for framework integrationLangChainAdapter: Transparent wrapper for LangChain runnables (chains, models)SmolAgentsAdapter: Transparent wrapper for SmolAgents agentsCodeAgentAdapter: Specialized wrapper for SmolAgents CodeAgent with enhanced trackingGenericAdapterandMiddlewareAdapterfor custom integrationscreate_adapter()factory function for any processing functionauto_adapter()automatically detects framework and creates appropriate adapter
- Fluent Interface:
rails.when(condition).inject(message)chains - Transparent Wrappers: Adapters intercept methods via
__getattr__while preserving original API - Context Variables:
current_rails()for global access within tools - Thread Pool Execution: Sync methods run async Rails code in separate threads to avoid event loop conflicts
- Generator-based Lifecycle: Using
yieldfor setup/teardown phases - Strategy Pattern: Different injection and execution strategies
- Decorator Pattern:
@lifecycle_functionfor modular components and@with_railsfor adapters
- All new features require corresponding tests in
tests/ - Use
pytest.mark.asynciofor async test functions - Mock external dependencies and API calls with proper patching
- Test both success and failure conditions
- Verify thread safety for Store operations
- Test lifecycle management with context managers
- For adapter tests: Use
@patchto mockFRAMEWORK_AVAILABLEflags - Mock framework classes to avoid requiring actual framework installations
- Test wrapper behavior: method interception, context management, metrics tracking
- Use proper condition builders (
counter(),state(),queue()) in tests, not lambda conditions with sync methods
- Implement the
Conditionprotocol inconditions.py - Add convenience helper function if appropriate
- Update
__all__inconditions.py - Add tests in
test_rails.py
- Implement the
Injectorprotocol ininjectors.py - Add convenience helper function
- Update
__all__ininjectors.py - Add integration test showing usage
Modern adapters use transparent wrapping:
- Extend
BaseAdapterinadapters/base.py - Implement
wrap()method that returns a wrapper class - Wrapper class uses
__getattr__to proxy all methods to original object - Intercept key methods (e.g.,
invoke,run) to inject Rails processing - Use thread pools to run async Rails code in sync methods
- Add example in
examples/adapters_demo.pyor create new example file - For official adapters (LangChain, SmolAgents), add to
src/rails/adapters/
- Use
@lifecycle_functiondecorator - Implement setup before
yield, cleanup after - Register in
LifecycleRegistryif built-in - Document priority and dependencies
- Always use async methods in async contexts
- Use sync methods (ending in
_sync) in tools or synchronous code - Available sync methods:
increment_sync(key, amount=1)- Increment counterget_counter_sync(key, default=0)- Get counter valueget_sync(key, default=None)- Get state valueset_sync(key, value)- Set state valuepush_queue_sync(queue, item)- Add to queue
- Queue operations are FIFO by default, configure in
QueueConfigfor LIFO - Counter operations are atomic and thread-safe
- State values support any JSON-serializable data
from rails import current_rails
def my_tool(data):
rails = current_rails() # Access Rails from within tool
rails.store.increment_sync('tool_calls')
rails.store.push_queue_sync('tasks', data['id'])
# Check current state
error_count = rails.store.get_counter_sync('errors')
if error_count > 5:
rails.store.set_sync('mode', 'careful')
# Tool logic here
return result- Async-first design: Prefer async methods, provide sync wrappers where needed
- Type hints: All public APIs must have complete type annotations
- Error handling: Use specific exceptions, never silent failures
- Thread safety: Store operations must be thread-safe
- Documentation: All public functions need docstrings with examples
- Testing: New features require tests before merging
- Backwards compatibility: Follow semantic versioning
- Message format: Use
Message(role=Role.X, content="...")for type safety - Condition evaluation: Conditions must implement async
evaluate(store)method - Context management: Always use
async with Rails()for proper lifecycle
Gemini-specific instructions