Environment Management

Environment management in HORUS allows you to capture, save, and restore the exact set of packages and versions used in your project. Perfect for reproducible builds, team collaboration, and deployment.

Overview

Think of environments like Python's requirements.txt or Node's package-lock.json:

  • Freeze your current environment to a file
  • Restore environments on different machines
  • Share setups with teammates
  • Deploy exact versions to production robots
  • Track environment history in version control

Understanding HORUS's Hidden Environment

HORUS automatically manages a hidden .horus/ environment in each project. Understanding the global vs local architecture helps you work efficiently across multiple projects.

Global Cache (Shared Storage)

Location: ~/.horus/cache/

All packages are downloaded once and stored here:

~/.horus/cache/
── horus_py@0.1.0/          # HORUS packages
   ── lib/horus/
── serde@1.0.228/           # External crates (from crates.io)
   ── src/
   ── Cargo.toml           # Cargo lives here, not your project!
   ── lib/libserde.rlib    # Pre-compiled
── pid-controller@1.2.0/    # More HORUS packages

Benefits:

  • Download once - Use in all projects
  • Saves disk space - No duplication
  • Faster setup - Cached packages install instantly
  • Works offline - Already have what you need

Local Workspace (Project-Specific)

Location: .horus/ in your project

Each project gets its own isolated environment:

my_robot_project/
── horus.toml               # Project config
── Cargo.toml               # Rust dependencies (or pyproject.toml for Python)
── main.rs / main.py        # Your code
── .horus/                  # Build cache (auto-managed)
    ── packages/            # Symlinks to global cache
       ── horus_py -> ~/.horus/cache/horus_py@0.1.0/
       ── pid-controller -> ~/.horus/cache/pid-controller@1.2.0/
    ── target/              # Build artifacts for Rust (auto-managed)

Key Points:

  • Workspace marker - .horus/ identifies a HORUS project
  • Symlinks not copies - Points to global cache (lightweight!)
  • Isolated - Each project independent
  • Auto-managed - Created by horus run, not by you

Automatic Workflow

When you run horus run:

  1. Reads horus.toml project config
  2. Detects language from native build files (Cargo.toml or pyproject.toml)
  3. Resolves HORUS registry packages to .horus/packages/
  4. Builds using the native toolchain (cargo build or Python runtime)
  5. Runs your code with the correct environment

You never touch .horus/ - It's automatic!

Why This Matters

Portable: horus.toml works on any machine

# Team member clones your project
git clone your-repo
cd your-repo
horus run  # Auto-installs dependencies and runs

Lightweight: Projects stay small

# Without HORUS (traditional)
project1/node_modules/  # 500 MB
project2/node_modules/  # 500 MB
project3/node_modules/  # 500 MB
# Total: 1.5 GB duplicated!

# With HORUS (global cache)
~/.horus/cache/         # 500 MB (shared)
project1/.horus/packages/  # Symlinks only (a few KB)
project2/.horus/packages/  # Symlinks only (a few KB)
project3/.horus/packages/  # Symlinks only (a few KB)
# Total: 500 MB globally, ~10 KB per project!

Isolated: Projects don't interfere

# Different versions supported
project_a/horus.toml:  serde@1.0.228
project_b/horus.toml:  serde@1.0.150
# Both work - isolated environments

Solving Dependency Hell

HORUS solves dependency hell with local-first resolution and smart fallback:

Resolution Order (how horus run finds packages):

  1. Check local first - .horus/packages/<package> exists? Use it immediately
  2. Check global cache - Only if not found locally
  3. Install from registry - Only if missing from both

Local Always Wins:

# Scenario: Global cache has broken package
~/.horus/cache/
── serde@1.0.228/  # Corrupted or incompatible

my_project/.horus/packages/
── serde@1.0.150/  # Working local version

# When you run:
horus run
#  Uses local serde@1.0.150 (line 1010-1012 in run.rs)
#  NEVER checks global cache
#  Broken global version ignored!

Automatic Smart Behavior:

When you install a package, HORUS automatically chooses the best strategy:

horus install serde

If package exists in global cache:

  • Install to global cache (save disk space)
  • Create symlink: .horus/packages/serde -> ~/.horus/cache/serde@1.0.228/
  • All projects share same version efficiently

If package NOT in global cache:

  • Install directly to local: .horus/packages/serde@1.0.228/
  • No symlink needed - real directory
  • Project is isolated from global cache

Override Broken Global Cache:

If global cache has corrupted or incompatible package:

# Option 1: Remove symlink and reinstall locally
rm .horus/packages/serde  # Remove symlink to broken global
horus install serde   # Installs locally (global check fails)

# Option 2: Install different version locally
horus install serde -v 1.0.150  # Specific working version

# Option 3: Clear global and reinstall
rm -rf ~/.horus/cache/serde@1.0.228/
horus run  # Auto-reinstalls to global

Benefits:

True isolation - Local packages override global No conflicts - Version-specific directories coexist Disk efficient - Uses global cache when possible Escape hatch - Can bypass broken global cache Zero config - Works automatically

Comparison with other tools:

FeaturePython venvNode node_modulesHORUS
Isolated envsFull copiesFull copiesSymlinks + local override
Disk efficientDuplicates everythingDuplicates everythingGlobal cache shared
Local overrideAlways localAlways localLocal-first, global fallback
Escape broken globalIndependentIndependentLocal override
Multiple versionsOne per venvNested hellVersion-specific dirs

Best of both worlds:

  • Disk efficiency of global cache (like Cargo, pip global)
  • Isolation of virtual environments (like venv, node_modules)
  • Smart automatic fallback

Quick Start

Save Your Environment

# Freeze to default file (horus-freeze.toml)
horus env freeze

# Freeze to custom file
horus env freeze -o production.toml

# Freeze and publish to registry
horus env freeze --publish

Restore an Environment

# Restore from local file
horus env restore horus-freeze.toml

# Restore from registry
horus env restore env_abc123

Environment Files

Format

Environment files use TOML format with complete package information:

horus-freeze.toml:

horus_id = "env_abc123def456"
name = "Mobile Robot Production Environment"
description = "Stable release for lab robots"
horus_version = "0.1.7"
created_at = "2025-10-09T14:32:15Z"

[system]
os = "Linux"
arch = "x86_64"
rust_version = "1.80.0"
python_version = "3.12.0"

[[packages]]
name = "pid-controller"
version = "1.2.0"
source = "Registry"
checksum = "sha256:a3b2c1..."

[[packages]]
name = "motion-planner"
version = "2.0.1"
source = "Registry"
checksum = "sha256:d4e5f6..."

[[packages]]
name = "sensor-drivers"
version = "1.5.0"
source = "Registry"
checksum = "sha256:g7h8i9..."

File Locations

Default locations:

my_project/
── horus-freeze.toml       # Default freeze file
── production.toml         # Custom freeze file
── environments/           # Optional: organize freeze files
   ── dev.toml
   ── staging.toml
   ── production.toml
── .horus/                 # Auto-managed, don't store freeze files here
    ── packages/           # Symlinks to global cache
    ── bin/                # Compiled binaries
    ── lib/                # Libraries
    ── include/            # Headers

Note: Store freeze files in your project root or a custom environments/ folder (not inside .horus/). The .horus/ directory is auto-managed and should not contain user files.

Freezing Environments

horus env freeze

Captures the current state of all installed packages.

Usage:

horus env freeze [OPTIONS]

Options:

  • -o, --output <FILE> - Output file (default: horus-freeze.toml)
  • --publish - Upload to registry and generate ID

Basic Freeze

horus env freeze

Output:

Analyzing environment...
   Found 5 packages (3 direct, 2 transitive)
   Verified checksums
   Generated metadata

Saved to: horus-freeze.toml

Packages frozen:
  pid-controller@1.2.0
  motion-planner@2.0.1
  sensor-drivers@1.5.0
  pathfinding-utils@1.2.0 (transitive)
  control-utils@1.0.0 (transitive)

To restore this environment:
  horus env restore horus-freeze.toml

Custom Output File

# Save to specific file
horus env freeze -o environments/production.toml

# Timestamp-based filename
horus env freeze -o "backup-$(date +%Y%m%d).toml"

Publish to Registry

# Freeze and upload to registry
horus env freeze --publish

Output:

Freezing environment...
   Captured 5 packages
   Generated manifest

Uploading to registry...
   Uploaded

Environment published:
  ID: env_abc123def456
  URL: https://registry.horusrobotics.dev/environments/env_abc123def456

Share this environment:
  horus env restore env_abc123def456

Or save locally:
  horus env freeze -o local-copy.toml

Benefits of publishing:

  • No file transfer - Just share the environment ID
  • Version tracking - Registry keeps history
  • Team access - Anyone authenticated can restore
  • Immutable - Can't be changed after publishing

Restoring Environments

horus env restore

Installs the exact packages from a frozen environment.

Usage:

horus env restore <FILE_OR_ID>

Options:

Note: Additional options for restore are planned for future releases.

Restore from File

# Restore from local file
horus env restore horus-freeze.toml

Output:

Reading environment: horus-freeze.toml
  Name: Mobile Robot Production Environment
  Packages: 5 (3 direct, 2 transitive)
  Created: 2025-10-09

Restoring environment...
   pid-controller@1.2.0
   motion-planner@2.0.1
   sensor-drivers@1.5.0
   pathfinding-utils@1.2.0 (dependency)
   control-utils@1.0.0 (dependency)

Environment restored successfully!
  Location: .horus/packages/
  Total size: 2.3 MB

Restore from Registry

# Restore using environment ID
horus env restore env_abc123def456

Output:

Fetching environment from registry...
   Downloaded env_abc123def456

Name: Production v2.1
Description: Stable release for warehouse robots
Author: robotics-team
Packages: 5

Restoring...
   All packages installed

Environment ready!

Common Workflows

Development Workflow

Setup for new developer:

# 1. Clone project
git clone https://github.com/team/robot-controller
cd robot-controller

# 2. Restore environment
horus env restore horus-freeze.toml

# 3. Run project
horus run --release

Update dependencies:

# 1. Install new package
horus install new-feature

# 2. Test
horus run --release

# 3. Freeze updated environment
horus env freeze

# 4. Commit changes
git add horus-freeze.toml
git commit -m "Add new-feature package"

Production Deployment

Prepare for deployment:

# 1. Test locally
horus run --release

# 2. Freeze environment
horus env freeze -o production.toml

# 3. Publish to registry
horus env freeze --publish
# Output: env_abc123def456

# 4. Deploy to robot
ssh robot@192.168.1.100
horus env restore env_abc123def456
horus run --release

Team Collaboration

Share exact setup:

# Team lead freezes and publishes environment
horus env freeze --publish
# Output: env_team_dev_001

# Team members restore
horus env restore env_team_dev_001

Multiple Environments

Maintain separate environments:

# Development
horus env freeze -o environments/dev.toml

# Staging
horus env freeze -o environments/staging.toml

# Production
horus env freeze -o environments/production.toml

Switch environments:

# Switch to staging
horus env restore environments/staging.toml

# Switch to production
horus env restore environments/production.toml

Rollback

If deployment fails, rollback:

# Restore previous working environment
horus env restore backup-20251008.toml

# Or restore from registry
horus env restore env_previous_stable

Environment Comparison

Note: Environment comparison commands (env diff) are planned for future releases. For now, you can manually compare freeze files or use standard diff tools.

Best Practices

Version Control

Always commit environment files:

# Add to git
git add horus-freeze.toml
git commit -m "Update environment: add sensor-fusion package"

# .gitignore (auto-generated by `horus new`)
.horus/               # Don't commit build cache

Environment Naming

Use descriptive names:

# Good
name = "Production v2.1 - Warehouse Robots"
name = "Development - Feature Branch XYZ"
name = "Staging - Pre-release Testing"

# Bad
name = "env1"
name = "test"
name = "final_final_v2"

Documentation

Document environment purpose:

name = "Production v2.1"
description = """
Stable production environment for warehouse robots.

Features:
- PID controller with anti-windup
- Motion planner with obstacle avoidance
- Sensor drivers for LIDAR and IMU

Tested on:
- Robot A (192.168.1.100)
- Robot B (192.168.1.101)
- Robot C (192.168.1.102)

Last updated: 2025-10-09
Contact: robotics-team@company.com
"""

Periodic Freezes

Freeze regularly:

# Before major changes
horus env freeze -o backup-before-upgrade.toml

# After successful testing
horus env freeze -o stable-$(date +%Y%m%d).toml

# Before deployment
horus env freeze -o production-v$(git describe --tags).toml

Environment Hygiene

Keep environments clean:

# Remove unused packages
horus list
horus remove unused-package

# Refreeze
horus env freeze

Troubleshooting

Package Not Available

Error:

Error: Package 'legacy-driver@0.5.0' not found in registry

Causes:

  • Package was unpublished
  • Version no longer available
  • Registry connection issue

Solutions:

# Option 1: Update environment file
# Remove or replace unavailable package

# Option 2: Install alternative
horus install modern-driver
horus env freeze  # Update freeze file

# Option 3: Use cached version
ls ~/.horus/cache/legacy-driver@0.5.0/

Checksum Mismatch

Error:

Error: Checksum mismatch for 'pid-controller@1.2.0'
  Expected: sha256:a3b2c1...
  Got:      sha256:x9y8z7...

Causes:

  • Package was modified on registry
  • Corrupted download
  • Network issue

Solutions:

# Reinstall the package
horus remove pid-controller
horus install pid-controller -v 1.2.0

Version Conflicts

Error:

Error: Cannot satisfy version constraints
  motion-planner requires pathfinding-utils ^1.2
  sensor-fusion requires pathfinding-utils ^1.0

Solutions:

# Option 1: Install compatible version
horus install sensor-fusion -v 2.0.0

# Option 2: Edit environment file manually
# Change pathfinding-utils version to compatible one

# Option 3: Remove conflicting package
horus remove sensor-fusion
horus env freeze  # Update freeze file

Registry Unavailable

Error:

Error: Failed to fetch environment from registry

Solutions:

# Use local file instead
horus env restore horus-freeze.toml

List Published Environments

horus env list

Show Environment Details

horus env show <environment-id>

Note: Additional environment management commands (env delete, env export) are planned for future releases.

Ignoring Files and Packages

The ignore Section

During development, you may have experimental code, debug files, or development-only packages that you don't want HORUS to process during horus run or horus check. The ignore section in horus.toml allows you to exclude these items.

Configuration

Add an [ignore] section to your horus.toml:

[package]
name = "my_robot_project"
version = "0.1.0"

# Optional: Ignore files and directories
[ignore]
files = [
  "debug_*.py",          # Ignore all files starting with debug_
  "test_*.rs",           # Ignore all test files
  "**/experiments/**",   # Ignore anything in experiments directories
]
directories = [
  "old/",                # Ignore old/ directory
  "experiments/",        # Ignore experiments/ directory
]

Pattern Matching

The ignore feature supports flexible pattern matching:

Wildcard (*):

  • debug_*.py - Matches debug_test.py, debug_node.py, etc.
  • test_* - Matches any file starting with test_

Recursive directory (**/):

  • **/experiments/** - Matches files in any experiments/ directory at any depth
  • **/temp/** - Matches any temp/ directory anywhere in the project

Exact match:

  • old/ - Matches the old/ directory exactly
  • debug.py - Matches only debug.py

Use Cases

Development files:

[ignore]
files = [
  "debug_*.py",
  "scratch_*.rs",
  "**/temp/**",
]

Test and experimental code:

[ignore]
directories = [
  "tests/",
  "experiments/",
  "benchmarks/",
]

Behavior

When files/directories are ignored:

  • File detection: Ignored files won't be detected as main files by horus run
  • Glob patterns: Ignored files are excluded from glob patterns like horus run "*.py"
  • Multi-file execution: Ignored files are skipped when running multiple files

Example Workflow

1. Create a project with debug files:

horus new my_project --python
cd my_project

2. Add debug files during development:

# Create some debug/experimental files
touch debug_sensor_test.py
mkdir experiments
touch experiments/new_algorithm.py

3. Update horus.toml to ignore them:

[ignore]
files = [
  "debug_*.py",
]
directories = [
  "experiments/",
]

4. Run the project:

horus run
# Only main.py runs, debug files and experiments are ignored

5. Explicitly run ignored files when needed:

horus run debug_sensor_test.py
# Ignored files can still be run explicitly

Best Practices

Keep it minimal: Only ignore what's necessary. Over-ignoring can make debugging harder.

Use version control: Commit your horus.toml with ignore patterns so team members have consistent behavior:

git add horus.toml
git commit -m "Add ignore patterns for debug files"

Document why: Add comments explaining why certain patterns are ignored:

[ignore]
# Legacy code being phased out
directories = [
  "old_controllers/",
]

Next Steps