diff --git a/cli.py b/cli.py index a1ca430c11..a02dcb9eed 100644 --- a/cli.py +++ b/cli.py @@ -7000,7 +7000,7 @@ class HermesCLI: logging.getLogger(noisy).setLevel(logging.WARNING) else: logging.getLogger().setLevel(logging.INFO) - for quiet_logger in ('tools', 'run_agent', 'trajectory_compressor', 'cron', 'hermes_cli'): + for quiet_logger in ('tools', 'run_agent', 'scripts.trajectory_compressor', 'cron', 'hermes_cli'): logging.getLogger(quiet_logger).setLevel(logging.ERROR) def _show_insights(self, command: str = "/insights"): diff --git a/datagen-config-examples/run_browser_tasks.sh b/datagen-config-examples/run_browser_tasks.sh index a66e416d9a..f31d657cea 100755 --- a/datagen-config-examples/run_browser_tasks.sh +++ b/datagen-config-examples/run_browser_tasks.sh @@ -29,7 +29,7 @@ echo "šŸ“ Logging to: $LOG_FILE" # Point to the example dataset in this directory SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" -python batch_runner.py \ +python scripts/batch_runner.py \ --dataset_file="$SCRIPT_DIR/example_browser_tasks.jsonl" \ --batch_size=5 \ --run_name="browser_tasks_example" \ diff --git a/datagen-config-examples/web_research.yaml b/datagen-config-examples/web_research.yaml index 6275dbed69..50ee43156e 100644 --- a/datagen-config-examples/web_research.yaml +++ b/datagen-config-examples/web_research.yaml @@ -4,7 +4,7 @@ # Generates tool-calling trajectories for multi-step web research tasks. # # Usage: -# python batch_runner.py \ +# python scripts/batch_runner.py \ # --config datagen-config-examples/web_research.yaml \ # --run_name web_research_v1 diff --git a/hermes_logging.py b/hermes_logging.py index 0ebc450a22..4cd0dc2bc3 100644 --- a/hermes_logging.py +++ b/hermes_logging.py @@ -142,7 +142,7 @@ class _ComponentFilter(logging.Filter): # Used by _ComponentFilter and exposed for ``hermes logs --component``. COMPONENT_PREFIXES = { "gateway": ("gateway",), - "agent": ("agent", "run_agent", "model_tools", "batch_runner"), + "agent": ("agent", "run_agent", "model_tools", "scripts.batch_runner"), "tools": ("tools",), "cli": ("hermes_cli", "cli"), "cron": ("cron",), diff --git a/pyproject.toml b/pyproject.toml index bd83673651..efe8d87c0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -120,13 +120,13 @@ hermes-agent = "run_agent:main" hermes-acp = "acp_adapter.entry:main" [tool.setuptools] -py-modules = ["run_agent", "model_tools", "toolsets", "batch_runner", "trajectory_compressor", "toolset_distributions", "cli", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "rl_cli", "utils"] +py-modules = ["run_agent", "model_tools", "toolsets", "toolset_distributions", "cli", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "utils"] [tool.setuptools.package-data] hermes_cli = ["web_dist/**/*"] [tool.setuptools.packages.find] -include = ["agent", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*"] +include = ["agent", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "scripts"] [tool.pytest.ini_options] testpaths = ["tests"] diff --git a/run_agent.py b/run_agent.py index 96033179b2..4f27f2a171 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1041,7 +1041,7 @@ class AIAgent: for quiet_logger in [ 'tools', # all tools.* (terminal, browser, web, file, etc.) 'run_agent', # agent runner internals - 'trajectory_compressor', + 'scripts.trajectory_compressor', 'cron', # scheduler (only relevant in daemon mode) 'hermes_cli', # CLI helpers ]: diff --git a/scripts/__init__.py b/scripts/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/batch_runner.py b/scripts/batch_runner.py similarity index 99% rename from batch_runner.py rename to scripts/batch_runner.py index 7413ad59f4..1cc3a12d2a 100644 --- a/batch_runner.py +++ b/scripts/batch_runner.py @@ -20,9 +20,13 @@ Usage: python batch_runner.py --dataset_file=data.jsonl --batch_size=10 --run_name=my_run --distribution=image_gen """ +import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + import json import logging -import os import time from pathlib import Path from typing import List, Dict, Any, Optional, Tuple diff --git a/mini_swe_runner.py b/scripts/mini_swe_runner.py similarity index 99% rename from mini_swe_runner.py rename to scripts/mini_swe_runner.py index c434515045..5316eabda4 100644 --- a/mini_swe_runner.py +++ b/scripts/mini_swe_runner.py @@ -26,10 +26,13 @@ Usage: python mini_swe_runner.py --prompts_file prompts.jsonl --output_file trajectories.jsonl --env docker """ -import json -import logging import os import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import json +import logging import time import uuid from datetime import datetime diff --git a/rl_cli.py b/scripts/rl_cli.py similarity index 99% rename from rl_cli.py rename to scripts/rl_cli.py index 78f86d4e04..fe414fde1f 100644 --- a/rl_cli.py +++ b/scripts/rl_cli.py @@ -19,9 +19,12 @@ Environment Variables: OPENROUTER_API_KEY: API key for OpenRouter (required for agent) """ -import asyncio import os import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import asyncio from pathlib import Path import fire @@ -32,7 +35,7 @@ from hermes_constants import get_hermes_home, OPENROUTER_BASE_URL # Load .env from ~/.hermes/.env first, then project root as dev fallback. # User-managed env files should override stale shell exports on restart. _hermes_home = get_hermes_home() -_project_env = Path(__file__).parent / '.env' +_project_env = Path(__file__).parent.parent / '.env' from hermes_cli.env_loader import load_hermes_dotenv diff --git a/scripts/sample_and_compress.py b/scripts/sample_and_compress.py index a6358f45b5..97d9217bb1 100644 --- a/scripts/sample_and_compress.py +++ b/scripts/sample_and_compress.py @@ -267,7 +267,7 @@ def run_compression(input_dir: Path, output_dir: Path, config_path: str): # Import the compressor import sys sys.path.insert(0, str(Path(__file__).parent.parent)) - from trajectory_compressor import TrajectoryCompressor, CompressionConfig + from scripts.trajectory_compressor import TrajectoryCompressor, CompressionConfig print(f"\nšŸ—œļø Running trajectory compression...") print(f" Input: {input_dir}") diff --git a/trajectory_compressor.py b/scripts/trajectory_compressor.py similarity index 99% rename from trajectory_compressor.py rename to scripts/trajectory_compressor.py index 33c5fc4327..7711e59a86 100644 --- a/trajectory_compressor.py +++ b/scripts/trajectory_compressor.py @@ -30,8 +30,12 @@ Usage: python trajectory_compressor.py --input=data/my_run --sample_percent=10 """ -import json import os +import sys + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import json import time import yaml import logging @@ -52,7 +56,7 @@ from agent.retry_utils import jittered_backoff from hermes_cli.env_loader import load_hermes_dotenv _hermes_home = get_hermes_home() -_project_env = Path(__file__).parent / ".env" +_project_env = Path(__file__).parent.parent / ".env" load_hermes_dotenv(hermes_home=_hermes_home, project_env=_project_env) diff --git a/tests/hermes_cli/test_arcee_provider.py b/tests/hermes_cli/test_arcee_provider.py index 39b4e57876..a3dd6e1e67 100644 --- a/tests/hermes_cli/test_arcee_provider.py +++ b/tests/hermes_cli/test_arcee_provider.py @@ -164,7 +164,7 @@ class TestArceeURLMapping: assert "arceeai" in _PROVIDER_PREFIXES def test_trajectory_compressor_detects_arcee(self): - import trajectory_compressor as tc + import scripts.trajectory_compressor as tc comp = tc.TrajectoryCompressor.__new__(tc.TrajectoryCompressor) comp.config = types.SimpleNamespace(base_url="https://api.arcee.ai/api/v1") assert comp._detect_provider() == "arcee" diff --git a/tests/integration/test_batch_runner.py b/tests/integration/test_batch_runner.py index 85565ae6e4..c462065462 100644 --- a/tests/integration/test_batch_runner.py +++ b/tests/integration/test_batch_runner.py @@ -104,7 +104,7 @@ def main(): test_file = create_test_dataset() print(f"\nšŸ“ To run the test manually:") - print(f" python batch_runner.py \\") + print(f" python scripts/batch_runner.py \\") print(f" --dataset_file={test_file} \\") print(f" --batch_size=2 \\") print(f" --run_name={run_name} \\") @@ -112,7 +112,7 @@ def main(): print(f" --num_workers=2") print(f"\nšŸ’” Or test with different distributions:") - print(f" python batch_runner.py --list_distributions") + print(f" python scripts/batch_runner.py --list_distributions") print(f"\nšŸ” After running, you can verify output with:") print(f" python tests/test_batch_runner.py --verify") diff --git a/tests/integration/test_checkpoint_resumption.py b/tests/integration/test_checkpoint_resumption.py index a5b1a2aa99..1d444e0e80 100644 --- a/tests/integration/test_checkpoint_resumption.py +++ b/tests/integration/test_checkpoint_resumption.py @@ -30,7 +30,7 @@ from pathlib import Path from typing import List, Dict, Any import traceback -# Add project root to path to import batch_runner +# Add project root to path to import scripts.batch_runner sys.path.insert(0, str(Path(__file__).parent.parent.parent)) @@ -135,7 +135,7 @@ def test_current_implementation(): shutil.rmtree(output_dir) # Import here to avoid issues if module changes - from batch_runner import BatchRunner + from scripts.batch_runner import BatchRunner checkpoint_file = output_dir / "checkpoint.json" @@ -229,7 +229,7 @@ def test_interruption_and_resume(): if output_dir.exists(): shutil.rmtree(output_dir) - from batch_runner import BatchRunner + from scripts.batch_runner import BatchRunner checkpoint_file = output_dir / "checkpoint.json" diff --git a/tests/test_batch_runner_checkpoint.py b/tests/test_batch_runner_checkpoint.py index 440e421cc5..6e5cc54c67 100644 --- a/tests/test_batch_runner_checkpoint.py +++ b/tests/test_batch_runner_checkpoint.py @@ -8,11 +8,7 @@ from unittest.mock import patch, MagicMock import pytest -# batch_runner uses relative imports, ensure project root is on path -import sys -sys.path.insert(0, str(Path(__file__).parent.parent)) - -from batch_runner import BatchRunner, _process_batch_worker +from scripts.batch_runner import BatchRunner, _process_batch_worker @pytest.fixture @@ -173,7 +169,7 @@ class TestBatchWorkerResumeBehavior: "toolsets_used": [], } - monkeypatch.setattr("batch_runner._process_single_prompt", lambda *args, **kwargs: prompt_result) + monkeypatch.setattr("scripts.batch_runner._process_single_prompt", lambda *args, **kwargs: prompt_result) result = _process_batch_worker(( 1, diff --git a/tests/test_mini_swe_runner.py b/tests/test_mini_swe_runner.py index 16ef262861..e1326c05b0 100644 --- a/tests/test_mini_swe_runner.py +++ b/tests/test_mini_swe_runner.py @@ -14,7 +14,7 @@ def test_run_task_kimi_omits_temperature(): ) mock_openai.return_value = client - from mini_swe_runner import MiniSWERunner + from scripts.mini_swe_runner import MiniSWERunner runner = MiniSWERunner( model="kimi-for-coding", @@ -42,7 +42,7 @@ def test_run_task_public_moonshot_kimi_k2_5_omits_temperature(): ) mock_openai.return_value = client - from mini_swe_runner import MiniSWERunner + from scripts.mini_swe_runner import MiniSWERunner runner = MiniSWERunner( model="kimi-k2.5", diff --git a/tests/test_trajectory_compressor.py b/tests/test_trajectory_compressor.py index 7978aab4c2..37ae1637d2 100644 --- a/tests/test_trajectory_compressor.py +++ b/tests/test_trajectory_compressor.py @@ -9,7 +9,7 @@ from unittest.mock import AsyncMock, patch, MagicMock import pytest -from trajectory_compressor import ( +from scripts.trajectory_compressor import ( CompressionConfig, TrajectoryMetrics, AggregateMetrics, @@ -25,8 +25,8 @@ def test_import_loads_env_from_hermes_home(tmp_path, monkeypatch): monkeypatch.setenv("HERMES_HOME", str(home)) monkeypatch.delenv("OPENROUTER_API_KEY", raising=False) - sys.modules.pop("trajectory_compressor", None) - importlib.import_module("trajectory_compressor") + sys.modules.pop("scripts.trajectory_compressor", None) + importlib.import_module("scripts.trajectory_compressor") assert os.getenv("OPENROUTER_API_KEY") == "from-hermes-home" diff --git a/tests/test_trajectory_compressor_async.py b/tests/test_trajectory_compressor_async.py index 369b980b8f..1d4870592b 100644 --- a/tests/test_trajectory_compressor_async.py +++ b/tests/test_trajectory_compressor_async.py @@ -22,7 +22,7 @@ class TestAsyncClientLazyCreation: def test_async_client_none_after_init(self): """async_client should be None after __init__ (not eagerly created).""" - from trajectory_compressor import TrajectoryCompressor + from scripts.trajectory_compressor import TrajectoryCompressor comp = TrajectoryCompressor.__new__(TrajectoryCompressor) comp.config = MagicMock() @@ -36,7 +36,7 @@ class TestAsyncClientLazyCreation: def test_get_async_client_creates_new_client(self): """_get_async_client() should create a fresh AsyncOpenAI instance.""" - from trajectory_compressor import TrajectoryCompressor + from scripts.trajectory_compressor import TrajectoryCompressor comp = TrajectoryCompressor.__new__(TrajectoryCompressor) comp.config = MagicMock() @@ -57,7 +57,7 @@ class TestAsyncClientLazyCreation: def test_get_async_client_creates_fresh_each_call(self): """Each call to _get_async_client() creates a NEW client instance, so it binds to the current event loop.""" - from trajectory_compressor import TrajectoryCompressor + from scripts.trajectory_compressor import TrajectoryCompressor comp = TrajectoryCompressor.__new__(TrajectoryCompressor) comp.config = MagicMock() @@ -91,7 +91,7 @@ class TestSourceLineVerification: def _read_file() -> str: import os base = os.path.dirname(os.path.dirname(__file__)) - with open(os.path.join(base, "trajectory_compressor.py")) as f: + with open(os.path.join(base, "scripts", "trajectory_compressor.py")) as f: return f.read() def test_no_eager_async_openai_in_init(self): @@ -119,7 +119,7 @@ class TestSourceLineVerification: @pytest.mark.asyncio async def test_generate_summary_async_kimi_omits_temperature(): """Kimi models should have temperature omitted — server manages it.""" - from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics + from scripts.trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics config = CompressionConfig( summarization_model="kimi-for-coding", @@ -147,7 +147,7 @@ async def test_generate_summary_async_kimi_omits_temperature(): @pytest.mark.asyncio async def test_generate_summary_async_public_moonshot_kimi_k2_5_omits_temperature(): """kimi-k2.5 on the public Moonshot API should not get a forced temperature.""" - from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics + from scripts.trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics config = CompressionConfig( summarization_model="kimi-k2.5", @@ -176,7 +176,7 @@ async def test_generate_summary_async_public_moonshot_kimi_k2_5_omits_temperatur @pytest.mark.asyncio async def test_generate_summary_async_public_moonshot_cn_kimi_k2_5_omits_temperature(): """kimi-k2.5 on api.moonshot.cn should not get a forced temperature.""" - from trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics + from scripts.trajectory_compressor import CompressionConfig, TrajectoryCompressor, TrajectoryMetrics config = CompressionConfig( summarization_model="kimi-k2.5", diff --git a/tests/tools/test_config_null_guard.py b/tests/tools/test_config_null_guard.py index a6ab64009c..4908eae752 100644 --- a/tests/tools/test_config_null_guard.py +++ b/tests/tools/test_config_null_guard.py @@ -87,7 +87,7 @@ class TestTrajectoryCompressorNullGuard: def test_null_base_url_does_not_crash(self): """base_url=None should not crash _detect_provider().""" - from trajectory_compressor import CompressionConfig, TrajectoryCompressor + from scripts.trajectory_compressor import CompressionConfig, TrajectoryCompressor config = CompressionConfig() config.base_url = None @@ -101,7 +101,7 @@ class TestTrajectoryCompressorNullGuard: def test_config_loading_null_base_url_keeps_default(self): """YAML ``summarization: {base_url: null}`` should keep default.""" - from trajectory_compressor import CompressionConfig + from scripts.trajectory_compressor import CompressionConfig from hermes_constants import OPENROUTER_BASE_URL config = CompressionConfig()