Development Guide
LibreFang development environment setup and contribution guidelines.
Development Environment
Prerequisites
- Rust 1.85+
- Node.js 22+ (for dashboard and desktop app)
- pnpm 10+ (for dashboard, docs, and web site)
- just (command runner, install guide)
Install just:
# macOS
brew install just
# Windows
winget install Casey.Just # or: scoop install just
# Linux (Debian/Ubuntu)
sudo apt install just # Ubuntu 24.04+, otherwise use cargo
# Any platform with Rust installed
cargo install just
Clone Repository
git clone https://github.com/librefang/librefang.git
cd librefang
Build with just (Recommended)
The project uses just as a command runner.
Run just to list all available recipes.
# Build + install CLI with dashboard (recommended for first-time setup)
just install
# Build workspace libraries only
just build
# Run all tests
just test
# Lint (must have zero clippy warnings)
just lint
# Local CI simulation: build + test + clippy + web lint
just ci
just install automatically builds the React dashboard before compiling the CLI binary,
so the dashboard will be fully functional on http://127.0.0.1:4545/.
The justfile uses [unix] and [windows] recipe attributes for cross-platform support. All recipes work on macOS, Linux, and Windows. On Windows, just install uses PowerShell commands automatically.
Build without just
# Build entire workspace (dashboard assets are pre-committed, no extra step needed)
cargo build --workspace
# Build CLI only
cargo build -p librefang-cli
# Build desktop app
cargo build -p librefang-desktop
First Run
# Initialize config (~/.librefang/config.toml)
librefang init
# Start the daemon
librefang start
# Open dashboard: http://127.0.0.1:4545/
Dashboard Development
The React dashboard is built locally into crates/librefang-api/static/react/ (gitignored).
CI builds and uploads it automatically; for local development, rebuild before compiling the Rust binary:
# Rebuild dashboard assets
just dashboard-build
# Or manually:
cd crates/librefang-api/dashboard
pnpm install --frozen-lockfile
pnpm run build
cd ../../..
cargo build -p librefang-cli
For active development with hot reload:
# Option A: start daemon + dashboard dev server together
just dev
# Option B: manually
librefang start # Terminal 1: backend daemon
cd crates/librefang-api/dashboard # Terminal 2: Vite dev server
pnpm install && pnpm dev
# Open http://localhost:5173/dashboard/ # Auto-proxies /api to 127.0.0.1:4545
Test
# Run all tests
cargo test --workspace
# Run specific crate
cargo test -p librefang-kernel
# Run doc tests
cargo test --doc
Linting
# Must have zero warnings
cargo clippy --workspace --all-targets -- -D warnings
# Format check
cargo fmt --all -- --check
Project Structure
librefang/
├── Cargo.lock
├── Cargo.toml
├── crates/
│ ├── librefang-cli/ # CLI tool
│ ├── librefang-api/ # REST API server
│ ├── librefang-kernel/ # Core kernel
│ ├── librefang-runtime/ # Agent runtime
│ ├── librefang-memory/ # Memory subsystem
│ ├── librefang-types/ # Shared types
│ ├── librefang-channels/ # Channel adapters
│ ├── librefang-skills/ # Skill system
│ ├── librefang-hands/ # Hands system
│ ├── librefang-wire/ # P2P protocol
│ ├── librefang-desktop/ # Desktop app
│ ├── librefang-migrate/ # Migration tools
│ └── librefang-extensions/ # Extensions
├── xtask/ # Build scripts
├── docs/ # Project documentation
└── scripts/ # Helper scripts
Adding New Channel
1. Create Channel Module
// crates/librefang-channels/src/my_channel.rs
use async_trait::async_trait;
use std::pin::Pin;
use futures::Stream;
use crate::types::{ChannelAdapter, ChannelContent, ChannelMessage, ChannelStatus, ChannelType, ChannelUser};
pub struct MyChannel {
// channel-specific fields (e.g. API client, config)
}
#[async_trait]
impl ChannelAdapter for MyChannel {
fn name(&self) -> &str {
"my_channel"
}
fn channel_type(&self) -> ChannelType {
ChannelType::Custom("my_channel".to_string())
}
async fn start(
&self,
) -> Result<
Pin<Box<dyn Stream<Item = ChannelMessage> + Send>>,
Box<dyn std::error::Error + Send + Sync>,
> {
// Return a stream of incoming messages
todo!()
}
async fn send(
&self,
_user: &ChannelUser,
_content: ChannelContent,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
// Send response back to the user
Ok(())
}
async fn stop(&self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
Ok(())
}
}
2. Register Channel
Channel adapters are registered with BridgeManager in librefang-api/src/server.rs
by calling bridge_manager.add_adapter(Arc::new(MyChannel::new(config))).
Each adapter is gated behind a Cargo feature flag (channel-my-channel) in
crates/librefang-channels/Cargo.toml.
// crates/librefang-channels/src/lib.rs — add the feature-gated module
#[cfg(feature = "channel-my-channel")]
pub mod my_channel;
3. Add Configuration
// crates/librefang-types/src/config.rs
#[derive(Debug, Clone, Deserialize)]
pub struct MyChannelConfig {
pub api_key_env: String,
pub allowed_users: Option<Vec<String>>,
}
Adding New Skill
1. Create Skill Structure
# skills/my-skill/skill.toml
name = "my-skill"
version = "1.0.0"
description = "My custom skill"
[runtime]
type = "python"
entrypoint = "main.py"
[tools]
provided = ["my_tool"]
[requirements]
packages = ["requests"]
2. Implement Code
# skills/my-skill/main.py
def my_tool(param: str) -> str:
"""My custom tool description"""
# Implement logic
return f"Result: {param}"
# Register tools
TOOLS = [my_tool]
3. Compile Skill
# Skills are automatically compiled into binary
cargo build --workspace
Adding New Tool
1. Define Tool
// crates/librefang-runtime/src/tools/mod.rs
use crate::tool::{Tool, ToolResult};
pub struct MyTool;
impl Tool for MyTool {
fn name(&self) -> &str {
"my_tool"
}
fn description(&self) -> &str {
"My custom tool description"
}
async fn execute(&self, params: Value) -> ToolResult {
// Implement tool logic
Ok(Value::String("result".to_string()))
}
}
2. Register Tool
// crates/librefang-runtime/src/lib.rs
pub fn register_tools(registry: &mut ToolRegistry) {
registry.register(MyTool::new());
}
Adding New LLM Provider
1. Implement Driver
// crates/librefang-runtime/src/llm/my_provider.rs
use crate::llm::{LlmDriver, LlmResponse, LlmError};
pub struct MyProvider {
api_key: String,
base_url: String,
}
#[async_trait]
impl LlmDriver for MyProvider {
async fn complete(&self, prompt: &str) -> Result<LlmResponse, LlmError> {
// Call API
Ok(LlmResponse {
text: "response".to_string(),
tokens: 100,
})
}
}
2. Add to Model Catalog
// crates/librefang-types/src/models.rs
pub fn get_provider(name: &str) -> Option<Box<dyn LlmDriver>> {
match name {
"my_provider" => Some(Box::new(MyProvider::new())),
_ => None,
}
}
Code Style
Rust Standards
- Format with
cargo fmt - Check with
cargo clippy - Follow Rust naming conventions
- Add documentation comments (
///)
Commit Standards
# Format: <type>(<scope>): <description>
git commit -m "feat(kernel): add new scheduling algorithm"
git commit -m "fix(channels): resolve Slack rate limit"
git commit -m "docs(api): update endpoint documentation"
git commit -m "test(runtime): add tool execution tests"
Types
| Type | Description |
|---|---|
| feat | New feature |
| fix | Bug fix |
| docs | Documentation |
| style | Formatting |
| refactor | Refactoring |
| test | Testing |
| chore | Maintenance |
Testing
Unit Tests
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_my_function() {
assert_eq!(my_function(2), 4);
}
}
Integration Tests
#[tokio::test]
async fn test_agent_spawn() {
let kernel = Kernel::new().await;
let agent = kernel.spawn("test-agent").await;
assert!(agent.is_running());
}
Benchmark Tests
#[tokio::bench]
async fn benchmark_llm_call(b: &mut Bencher) {
b.iter(|| {
runtime.block_on(llm.complete("test prompt"))
})
}
Debugging
Logging
use tracing::{info, warn, error};
info!("Starting agent {}", agent_id);
warn!("Rate limit exceeded for channel {}", channel_id);
error!("Failed to connect to provider: {}", error);
Debug Mode
# Enable verbose logging
RUST_LOG=debug cargo run
# See specific module only
RUST_LOG=librefang_kernel=trace cargo run
Dashboard Debugging
The dashboard is a React SPA embedded into the binary at compile time via include_dir!.
Asset files are served at /dashboard/*.
Blank page / assets 404:
# 1. Check that the daemon is running and healthy
curl -s http://127.0.0.1:4545/api/health
# 2. Verify the HTML is served (should return <!doctype html>...)
curl -s http://127.0.0.1:4545/ | head -5
# 3. Test a specific asset path (pick from the <script> tag in the HTML)
curl -sI http://127.0.0.1:4545/dashboard/assets/index-DVRukrPy.js
# 200 = OK, 404 = assets not embedded
# 4. If assets 404, rebuild the dashboard locally and recompile
just dashboard-build
cargo build -p librefang-cli --release
Dashboard shows but data is empty:
# Check API endpoints directly
curl -s http://127.0.0.1:4545/api/status | python3 -m json.tool
curl -s http://127.0.0.1:4545/api/agents | python3 -m json.tool
# Check browser console for CORS or auth errors (F12 → Console)
# Default install has no auth — if api_key is set, pass it:
curl -s -H "Authorization: Bearer YOUR_KEY" http://127.0.0.1:4545/api/status
Dashboard dev server can't reach API:
# Vite proxies /api to http://127.0.0.1:4545 — make sure daemon is running
librefang status
# If not running:
librefang start
Performance Profiling
# CPU profiling
cargo flamegraph --bin librefang-cli -- start
Release
Versioning
LibreFang uses Calendar Versioning — YYYY.M.DDHH.
| Type | Format | Example | Description |
|---|---|---|---|
| Stable | YYYY.M.DDHH | 2026.3.2314 | Day + hour of release |
| Beta | YYYY.M.DDHH-betaN | 2026.3.2314-beta1 | Pre-release for testing |
| RC | YYYY.M.DDHH-rcN | 2026.3.2314-rc1 | Release candidate |
| LTS | YYYY.M.PATCH-lts | 2026.3.0-lts | Long-term support |
Release Process
All releases are done via cargo xtask release:
cargo xtask release
# 1) stable -> 2026.3.2314
# 2) beta -> 2026.3.2314-beta1
# 3) rc -> 2026.3.2314-rc1
# 4) lts -> 2026.3.0-lts
This command automatically:
- Generates CHANGELOG from git history
- Syncs version across Cargo.toml, package.json, setup.py
- Builds React dashboard
- Creates git tag and push
- Opens a PR for the version bump
Use --dry-run to preview without making changes:
cargo xtask release --dry-run --version "2026.3.0-lts"
LTS Releases
LTS (Long-Term Support) versions receive only security and bug fixes. No new features.
Version scheme:
v2026.3.0-lts ← initial LTS
v2026.3.1-lts ← patch 1 (bug fix)
v2026.3.2-lts ← patch 2 (security fix)
Creating an LTS release:
# On main branch — choose option 4
cargo xtask release
CI automatically:
- Creates a
release/2026.3branch from the tag - Sets branch protection (requires PR review)
- Tags Docker image with
:lts - Marks the GitHub Release as LTS
Patching an LTS release:
# Switch to the LTS branch
git checkout release/2026.3
# Cherry-pick the fix from main
git cherry-pick <commit-sha>
# Release the patch (auto-increments patch number)
cargo xtask release --lts-patch
Rules:
- Only cherry-pick fixes, never new features
- Every patch must go through PR review on the
release/branch - LTS branches are maintained until the next LTS is declared
- Docker
:ltstag always points to the latest LTS release
Contributing
Contribution Process
- Fork the repository
- Create feature branch (
git checkout -b feature/my-feature) - Commit changes (
git commit -m "feat: add my feature") - Push branch (
git push origin feature/my-feature) - Create Pull Request
Code Review
- Ensure
cargo clippyhas no warnings - Ensure
cargo testpasses - Add test coverage for new code
- Update documentation
Code of Conduct
- Respect others
- Welcome newcomers
- Communicate professionally
- Accept constructive criticism