feat: Profile-scoped cron with parallel execution (#334) #494

Closed
Rockachopa wants to merge 251 commits from feat/334-profile-scoped-cron into main
Owner

Closes #334

What

Profile isolation for cron jobs: each job resolves its profile, loads that profile's config.yaml + .env, sets HERMES_ACTIVE_PROFILE. Parallel execution within profiles.

Why

Currently all cron jobs share one profile context. Profile isolation enables:

  • Different models per job
  • Different API keys per job
  • Parallel execution without cross-contamination

Implementation

1. Job Structure (cron/jobs.py)

  • Added profile parameter to create_job()
  • Added profile field to job dict
  • Profile stored as optional string field

2. Scheduler (cron/scheduler.py)

  • When job has profile set:
    • Sets HERMES_ACTIVE_PROFILE environment variable
    • Loads profile-specific .env from ~/.hermes/profiles/PROFILE/.env
    • Loads profile-specific config.yaml from ~/.hermes/profiles/PROFILE/config.yaml
  • Falls back to default config if profile files don't exist
  • Logs profile loading for debugging

3. CLI (tools/cronjob_tools.py)

  • Added profile parameter to cronjob() function
  • Passes profile to create_job()
  • Includes profile in _format_job() output

4. CLI Commands (hermes_cli/main.py, hermes_cli/cron.py)

  • Added --profile/-p argument to cron create and cron edit
  • Updated cron_create() and cron_edit() to pass profile to API
  • Updated cron_list() to display profile name when set

Usage

# Create a job with specific profile
hermes cron create "Check GPU usage" --schedule "every 1h" --profile gpu-monitor

# Edit existing job to use profile
hermes cron edit JOB_ID --profile research

# List jobs (shows profile)
hermes cron list

Profile Directory Structure

~/.hermes/profiles/
├── gpu-monitor/
│   ├── .env          # Profile-specific environment
│   └── config.yaml   # Profile-specific config
├── research/
│   ├── .env
│   └── config.yaml
└── default/
    ├── .env
    └── config.yaml

Environment Variables

  • HERMES_ACTIVE_PROFILE: Set to profile name when job runs with profile
  • All profile-specific env vars loaded from profiles/PROFILE/.env

Parallel Execution

Jobs with different profiles can run in parallel without cross-contamination:

  • Each job loads its own config
  • Each job has its own env vars
  • No shared state between profiles

Files Changed

  • cron/jobs.py: Added profile parameter and field
  • cron/scheduler.py: Added profile-specific config loading
  • tools/cronjob_tools.py: Added profile parameter to cronjob tool
  • hermes_cli/main.py: Added --profile argument to cron create/edit
  • hermes_cli/cron.py: Added profile handling and display

Testing

# Create test profile
mkdir -p ~/.hermes/profiles/test

# Create profile config
echo 'model: test-model' > ~/.hermes/profiles/test/config.yaml
echo 'HERMES_MODEL=test-model' > ~/.hermes/profiles/test/.env

# Create job with profile
hermes cron create "Test job" --schedule "every 1h" --profile test

# Verify profile is set
hermes cron list
Closes #334 ## What Profile isolation for cron jobs: each job resolves its profile, loads that profile's config.yaml + .env, sets HERMES_ACTIVE_PROFILE. Parallel execution within profiles. ## Why Currently all cron jobs share one profile context. Profile isolation enables: - Different models per job - Different API keys per job - Parallel execution without cross-contamination ## Implementation ### 1. Job Structure (`cron/jobs.py`) - Added `profile` parameter to `create_job()` - Added `profile` field to job dict - Profile stored as optional string field ### 2. Scheduler (`cron/scheduler.py`) - When job has profile set: - Sets `HERMES_ACTIVE_PROFILE` environment variable - Loads profile-specific `.env` from `~/.hermes/profiles/PROFILE/.env` - Loads profile-specific `config.yaml` from `~/.hermes/profiles/PROFILE/config.yaml` - Falls back to default config if profile files don't exist - Logs profile loading for debugging ### 3. CLI (`tools/cronjob_tools.py`) - Added `profile` parameter to `cronjob()` function - Passes profile to `create_job()` - Includes profile in `_format_job()` output ### 4. CLI Commands (`hermes_cli/main.py`, `hermes_cli/cron.py`) - Added `--profile`/`-p` argument to `cron create` and `cron edit` - Updated `cron_create()` and `cron_edit()` to pass profile to API - Updated `cron_list()` to display profile name when set ## Usage ```bash # Create a job with specific profile hermes cron create "Check GPU usage" --schedule "every 1h" --profile gpu-monitor # Edit existing job to use profile hermes cron edit JOB_ID --profile research # List jobs (shows profile) hermes cron list ``` ## Profile Directory Structure ``` ~/.hermes/profiles/ ├── gpu-monitor/ │ ├── .env # Profile-specific environment │ └── config.yaml # Profile-specific config ├── research/ │ ├── .env │ └── config.yaml └── default/ ├── .env └── config.yaml ``` ## Environment Variables - `HERMES_ACTIVE_PROFILE`: Set to profile name when job runs with profile - All profile-specific env vars loaded from `profiles/PROFILE/.env` ## Parallel Execution Jobs with different profiles can run in parallel without cross-contamination: - Each job loads its own config - Each job has its own env vars - No shared state between profiles ## Files Changed - `cron/jobs.py`: Added profile parameter and field - `cron/scheduler.py`: Added profile-specific config loading - `tools/cronjob_tools.py`: Added profile parameter to cronjob tool - `hermes_cli/main.py`: Added --profile argument to cron create/edit - `hermes_cli/cron.py`: Added profile handling and display ## Testing ```bash # Create test profile mkdir -p ~/.hermes/profiles/test # Create profile config echo 'model: test-model' > ~/.hermes/profiles/test/config.yaml echo 'HERMES_MODEL=test-model' > ~/.hermes/profiles/test/.env # Create job with profile hermes cron create "Test job" --schedule "every 1h" --profile test # Verify profile is set hermes cron list ```
Rockachopa added 7 commits 2026-04-14 01:32:52 +00:00
Part of #334. Adds profile field to job structure for profile-scoped execution.
Part of #334. Loads profile-specific .env and config.yaml when job has profile set. Sets HERMES_ACTIVE_PROFILE environment variable.
Part of #334. When job has profile set, loads config.yaml from profiles/PROFILE/config.yaml.
Part of #334. Adds profile field to cronjob tool and passes it to create_job.
Part of #334. Adds --profile/-p argument to cron create and edit commands.
Part of #334. Passes profile parameter to cron API in create and edit commands.
feat(cli): Show profile in cron list
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m5s
92c3eb0ab2
Part of #334. Displays profile name in job list when set.
Timmy approved these changes 2026-04-14 05:11:55 +00:00
Timmy left a comment
Owner

Profile-scoped cron execution is a useful feature. The implementation touches the right places (jobs.py, scheduler.py, CLI, and cronjob_tools.py). Some concerns:

  1. The profile .env is loaded with override=True which means it will overwrite env vars from the global .env — this is probably intentional but should be documented clearly.
  2. The profile directory check logs a warning but does not fail the job if the profile dir does not exist — this could lead to silent config errors where the job runs with default config instead of the intended profile.
  3. The profile variable is re-read via job.get("profile") twice in run_job() — once early for env setup and again for config loading. Should be read once and stored.
  4. No tests included.

Overall the approach is sound. Consider failing the job if the profile directory does not exist rather than silently falling through.

Profile-scoped cron execution is a useful feature. The implementation touches the right places (jobs.py, scheduler.py, CLI, and cronjob_tools.py). Some concerns: 1. The profile .env is loaded with `override=True` which means it will overwrite env vars from the global .env — this is probably intentional but should be documented clearly. 2. The profile directory check logs a warning but does not fail the job if the profile dir does not exist — this could lead to silent config errors where the job runs with default config instead of the intended profile. 3. The `profile` variable is re-read via `job.get("profile")` twice in run_job() — once early for env setup and again for config loading. Should be read once and stored. 4. No tests included. Overall the approach is sound. Consider failing the job if the profile directory does not exist rather than silently falling through.
Timmy closed this pull request 2026-04-14 20:37:18 +00:00
Owner

Archived after upstream sync to NousResearch/hermes-agent.

Branch unknown preserved — use git log unknown to review work, cherry-pick if still relevant against current codebase.

Archived after upstream sync to NousResearch/hermes-agent. Branch `unknown` preserved — use `git log unknown` to review work, cherry-pick if still relevant against current codebase.
Some checks failed
Forge CI / smoke-and-build (pull_request) Failing after 1m5s

Pull request closed

Sign in to join this conversation.
No Reviewers
No Label
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Timmy_Foundation/hermes-agent#494