Developer Guide
This guide is intended for developers who want to contribute to Cyclonetix or understand its internal architecture for extending its functionality.
Project Structure
The Cyclonetix codebase is organized as follows:
cyclonetix/
├── src/
│ ├── agent/ # Agent implementation
│ ├── graph/ # Execution graph and orchestrator
│ ├── models/ # Core data models
│ ├── server/ # Web UI and API
│ ├── state/ # State management
│ └── utils/ # Utility functions
├── static/ # Static web assets
├── templates/ # Tera templates for UI
├── docs/ # Documentation
└── data/ # Example data files
Architecture Overview
Cyclonetix follows a modular architecture with clear separation of concerns:
- Core Models (
src/models/
): Define the data structures used throughout the system. - State Management (
src/state/
): Abstracts storage backends and provides a unified interface. - Agent (
src/agent/
): Handles task execution and reporting. - Graph (
src/graph/
): Manages task dependencies and execution flow. - Server (
src/server/
): Provides the web UI and API.
Key Abstractions
StateManager Trait
The StateManager
trait (src/state/state_manager.rs
) is the central abstraction for state persistence:
#[async_trait]
pub trait StateManager: Sync + Send {
// Queue operations
async fn get_work_from_queue(&self, queue: &str) -> Option<TaskPayload>;
async fn put_work_on_queue(&self, task_payload: &TaskPayload, queue: &str);
// Task operations
async fn save_task(&self, task: &TaskTemplate);
async fn load_task(&self, task_id: &str) -> Option<TaskTemplate>;
// DAG operations
async fn save_dag_instance(&self, dag_instance: &DagInstance);
async fn load_dag_instance(&self, run_id: &str) -> Option<DagInstance>;
// many other methods...
}
This trait is implemented for different backends like Redis and in-memory storage.
ExecutionGraph
The ExecutionGraph
struct (src/graph/graph_manager.rs
) manages task dependencies and execution order:
pub struct ExecutionGraph {
pub graph: DiGraph<String, ()>,
pub node_map: HashMap<String, NodeIndex>,
}
It provides methods for building and traversing the execution graph.
Agent
The Agent
struct (src/agent/agent.rs
) is responsible for executing tasks:
pub struct Agent<S: 'static + StateManager + ?Sized> {
: Arc<S>,
state_manager: String,
agent_id}
Development Environment Setup
Prerequisites
- Rust 1.84+ with Cargo
- Redis (optional, for testing with Redis backend)
- Git
Setting Up a Development Environment
- Clone the repository:
git clone https://github.com/neural-chilli/Cyclonetix.git
cd Cyclonetix
- Build the project:
cargo build
- Run tests:
cargo test
- Run with development features:
DEV_MODE=true cargo run
UI Development
The UI is built with:
- Tera for templating
- Tabler for UI components
- Cytoscape.js for graph visualization
When DEV_MODE=true
, templates are reloaded on each request, making UI development faster.
How to Contribute
Finding Issues to Work On
Check the GitHub issues labeled with “good first issue” or “help wanted”.
Creating a New Feature
Create a branch:
git checkout -b feature/your-feature-name
Implement your changes:
- Keep each commit focused on a single change
- Follow the existing code style
- Add appropriate tests
Update documentation:
- Update or add Quarto documentation
- Add inline code comments for complex logic
Submit a PR:
- Describe your changes
- Reference any related issues
- Include screenshots for UI changes
Code Style and Best Practices
- Follow Rust’s official style guidelines
- Use async/await consistently for asynchronous code
- Ensure proper error handling
- Add debug and info logging at appropriate points
- Document public interfaces with doc comments
Adding a New Backend
To implement a new state backend (e.g., PostgreSQL):
- Create a new file
src/state/postgres_state_manager.rs
- Implement the
StateManager
trait - Add appropriate tests
- Update
main.rs
to include the new backend option
Example skeleton:
pub struct PostgresStateManager {
: PgPool,
pool: String,
cluster_id}
impl PostgresStateManager {
pub async fn new(db_url: &str, cluster_id: &str) -> Self {
let pool = PgPool::connect(db_url).await.expect("Failed to connect to PostgreSQL");
{
PostgresStateManager ,
pool: String::from(cluster_id),
cluster_id}
}
}
#[async_trait]
impl StateManager for PostgresStateManager {
// Implement all required methods
async fn get_work_from_queue(&self, queue: &str) -> Option<TaskPayload> {
// Implementation
}
// ... other methods
}
Testing
Running Tests
# Run all tests
cargo test
# Run specific tests
cargo test state_manager
# Run with feature flags
cargo test --features postgresql
Test Architecture
- Unit Tests: Test individual components in isolation
- Integration Tests: Test component interactions
- End-to-End Tests: Test complete workflows
Writing Good Tests
- Test both success and error paths
- Use descriptive test names
- Set up appropriate fixtures
- Clean up after tests (especially important for Redis/DB tests)
Debugging
Logging
Cyclonetix uses the tracing
crate for structured logging. To enable verbose logging:
RUST_LOG=debug cargo run
Common Issues
- Redis Connection: Ensure Redis is running and accessible
- Task Execution: Check agent logs for command execution errors
- UI Issues: Check browser console and server logs
API Documentation
Run cargo doc --open
to generate and view the API documentation.
Release Process
- Update Version: Update version in
Cargo.toml
- Update Changelog: Document changes in
CHANGELOG.md
- Create Tag: Tag the release in Git
- Build Release: Run
cargo build --release
- Create GitHub Release: Upload the binary
Next Steps
- Review the Roadmap to see planned features
- Check out Troubleshooting & FAQ for common issues
- Explore the Reference section for detailed specifications