refactor(restructure): update infrastructure for hermes_agent package

Update pyproject.toml entry points, packages.find, and package-data.
Delete py-modules (all top-level modules moved into hermes_agent/).
Add hermes-skills-sync console_script entry point.
Update Dockerfile HERMES_WEB_DIST path.
Update docker/entrypoint.sh, scripts/install.sh, setup-hermes.sh
to use hermes-skills-sync console_script.
Update web/vite.config.ts output directory.
Update MANIFEST.in to graft hermes_agent.
Update AGENTS.md project structure to reflect new layout.

Part of #14182, #14183
This commit is contained in:
alt-glitch 2026-04-23 12:10:25 +05:30
parent a1e667b9f2
commit 76aebd73c3
8 changed files with 78 additions and 88 deletions

137
AGENTS.md
View file

@ -12,68 +12,59 @@ source venv/bin/activate # ALWAYS activate before running Python
``` ```
hermes-agent/ hermes-agent/
├── run_agent.py # AIAgent class — core conversation loop ├── hermes_agent/ # Single installable package
├── model_tools.py # Tool orchestration, discover_builtin_tools(), handle_function_call() │ ├── agent/ # Core conversation loop and agent internals
├── toolsets.py # Toolset definitions, _HERMES_CORE_TOOLS list │ │ ├── loop.py # AIAgent class — core conversation loop
├── cli.py # HermesCLI class — interactive CLI orchestrator │ │ ├── prompt_builder.py # System prompt assembly
├── hermes_state.py # SessionDB — SQLite session store (FTS5 search) │ │ ├── context/ # Context management (engine, compressor, references)
├── agent/ # Agent internals │ │ ├── memory/ # Memory management (manager, provider)
│ ├── prompt_builder.py # System prompt assembly │ │ ├── image_gen/ # Image generation (provider, registry)
│ ├── context_compressor.py # Auto context compression │ │ ├── display.py # KawaiiSpinner, tool preview formatting
│ ├── prompt_caching.py # Anthropic prompt caching │ │ ├── skill_commands.py # Skill slash commands (shared CLI/gateway)
│ ├── auxiliary_client.py # Auxiliary LLM client (vision, summarization) │ │ └── trajectory.py # Trajectory saving helpers
│ ├── model_metadata.py # Model context lengths, token estimation │ ├── providers/ # LLM provider adapters and transports
│ ├── models_dev.py # models.dev registry integration (provider-aware context) │ │ ├── anthropic_adapter.py # Anthropic adapter
│ ├── display.py # KawaiiSpinner, tool preview formatting │ │ ├── anthropic_transport.py # Anthropic transport
│ ├── skill_commands.py # Skill slash commands (shared CLI/gateway) │ │ ├── metadata.py # Model context lengths, token estimation
│ └── trajectory.py # Trajectory saving helpers │ │ ├── auxiliary.py # Auxiliary LLM client (vision, summarization)
├── hermes_cli/ # CLI subcommands and setup │ │ ├── caching.py # Anthropic prompt caching
│ ├── main.py # Entry point — all `hermes` subcommands │ │ └── credential_pool.py # Credential management
│ ├── config.py # DEFAULT_CONFIG, OPTIONAL_ENV_VARS, migration │ ├── tools/ # Tool implementations
│ ├── commands.py # Slash command definitions + SlashCommandCompleter │ │ ├── dispatch.py # Tool orchestration, discover_builtin_tools()
│ ├── callbacks.py # Terminal callbacks (clarify, sudo, approval) │ │ ├── toolsets.py # Toolset definitions
│ ├── setup.py # Interactive setup wizard │ │ ├── registry.py # Central tool registry
│ ├── skin_engine.py # Skin/theme engine — CLI visual customization │ │ ├── terminal.py # Terminal orchestration
│ ├── skills_config.py # `hermes skills` — enable/disable skills per platform │ │ ├── browser/ # Browser tools (tool, cdp, camofox, providers/)
│ ├── tools_config.py # `hermes tools` — enable/disable tools per platform │ │ ├── mcp/ # MCP client and server
│ ├── skills_hub.py # `/skills` slash command (search, browse, install) │ │ ├── skills/ # Skill management (manager, tool, hub, guard, sync)
│ ├── models.py # Model catalog, provider model lists │ │ ├── media/ # Voice, TTS, transcription, image gen
│ ├── model_switch.py # Shared /model switch pipeline (CLI + gateway) │ │ ├── files/ # File operations (tools, operations, state)
│ └── auth.py # Provider credential resolution │ │ └── security/ # Path security, URL safety, approval
├── tools/ # Tool implementations (one file per tool) │ ├── backends/ # Terminal backends (local, docker, ssh, modal, daytona, singularity)
│ ├── registry.py # Central tool registry (schemas, handlers, dispatch) │ ├── cli/ # CLI subcommands and setup
│ ├── approval.py # Dangerous command detection │ │ ├── main.py # Entry point — all `hermes` subcommands
│ ├── terminal_tool.py # Terminal orchestration │ │ ├── repl.py # HermesCLI class — interactive CLI orchestrator
│ ├── process_registry.py # Background process management │ │ ├── config.py # DEFAULT_CONFIG, OPTIONAL_ENV_VARS, migration
│ ├── file_tools.py # File read/write/search/patch │ │ ├── commands.py # Slash command definitions
│ ├── web_tools.py # Web search/extract (Parallel + Firecrawl) │ │ ├── auth/ # Provider credential resolution
│ ├── browser_tool.py # Browserbase browser automation │ │ ├── models/ # Model catalog, provider lists, switching
│ ├── code_execution_tool.py # execute_code sandbox │ │ └── ui/ # Banner, colors, skin engine, callbacks, tips
│ ├── delegate_tool.py # Subagent delegation │ ├── gateway/ # Messaging platform gateway
│ ├── mcp_tool.py # MCP client (~1050 lines) │ │ ├── run.py # Main loop, slash commands, message dispatch
│ └── environments/ # Terminal backends (local, docker, ssh, modal, daytona, singularity) │ │ ├── session.py # SessionStore — conversation persistence
├── gateway/ # Messaging platform gateway │ │ └── platforms/ # Adapters: telegram, discord, slack, whatsapp, etc.
│ ├── run.py # Main loop, slash commands, message dispatch │ ├── acp/ # ACP server (VS Code / Zed / JetBrains integration)
│ ├── session.py # SessionStore — conversation persistence │ ├── cron/ # Scheduler (jobs.py, scheduler.py)
│ └── platforms/ # Adapters: telegram, discord, slack, whatsapp, homeassistant, signal, qqbot │ ├── plugins/ # Plugin system (memory providers, context engines)
├── ui-tui/ # Ink (React) terminal UI — `hermes --tui` │ ├── constants.py # Shared constants
│ ├── src/entry.tsx # TTY gate + render() │ ├── state.py # SessionDB — SQLite session store
│ ├── src/app.tsx # Main state machine and UI │ ├── logging.py # Logging configuration
│ ├── src/gatewayClient.ts # Child process + JSON-RPC bridge │ └── utils.py # Shared utilities
│ ├── src/app/ # Decomposed app logic (event handler, slash handler, stores, hooks)
│ ├── src/components/ # Ink components (branding, markdown, prompts, pickers, etc.)
│ ├── src/hooks/ # useCompletion, useInputHistory, useQueue, useVirtualHistory
│ └── src/lib/ # Pure helpers (history, osc52, text, rpc, messages)
├── tui_gateway/ # Python JSON-RPC backend for the TUI ├── tui_gateway/ # Python JSON-RPC backend for the TUI
│ ├── entry.py # stdio entrypoint ├── ui-tui/ # Ink (React) terminal UI — `hermes --tui`
│ ├── server.py # RPC handlers and session logic
│ ├── render.py # Optional rich/ANSI bridge
│ └── slash_worker.py # Persistent HermesCLI subprocess for slash commands
├── acp_adapter/ # ACP server (VS Code / Zed / JetBrains integration)
├── cron/ # Scheduler (jobs.py, scheduler.py)
├── environments/ # RL training environments (Atropos) ├── environments/ # RL training environments (Atropos)
├── tests/ # Pytest suite (~3000 tests) ├── tests/ # Pytest suite
└── batch_runner.py # Parallel batch processing └── web/ # Vite + React web dashboard
``` ```
**User config:** `~/.hermes/config.yaml` (settings), `~/.hermes/.env` (API keys) **User config:** `~/.hermes/config.yaml` (settings), `~/.hermes/.env` (API keys)
@ -81,18 +72,18 @@ hermes-agent/
## File Dependency Chain ## File Dependency Chain
``` ```
tools/registry.py (no deps — imported by all tool files) hermes_agent/tools/registry.py (no deps — imported by all tool files)
tools/*.py (each calls registry.register() at import time) hermes_agent/tools/*.py (each calls registry.register() at import time)
model_tools.py (imports tools/registry + triggers tool discovery) hermes_agent/tools/dispatch.py (imports registry + triggers tool discovery)
run_agent.py, cli.py, batch_runner.py, environments/ hermes_agent/agent/loop.py, hermes_agent/cli/repl.py, environments/
``` ```
--- ---
## AIAgent Class (run_agent.py) ## AIAgent Class (hermes_agent/agent/loop.py)
```python ```python
class AIAgent: class AIAgent:
@ -138,14 +129,14 @@ Messages follow OpenAI format: `{"role": "system/user/assistant/tool", ...}`. Re
--- ---
## CLI Architecture (cli.py) ## CLI Architecture (hermes_agent/cli/repl.py)
- **Rich** for banner/panels, **prompt_toolkit** for input with autocomplete - **Rich** for banner/panels, **prompt_toolkit** for input with autocomplete
- **KawaiiSpinner** (`agent/display.py`) — animated faces during API calls, `┊` activity feed for tool results - **KawaiiSpinner** (`hermes_agent/agent/display.py`) — animated faces during API calls, `┊` activity feed for tool results
- `load_cli_config()` in cli.py merges hardcoded defaults + user config YAML - `load_cli_config()` in repl.py merges hardcoded defaults + user config YAML
- **Skin engine** (`hermes_cli/skin_engine.py`) — data-driven CLI theming; initialized from `display.skin` config key at startup; skins customize banner colors, spinner faces/verbs/wings, tool prefix, response box, branding text - **Skin engine** (`hermes_agent/cli/ui/skin_engine.py`) — data-driven CLI theming; initialized from `display.skin` config key at startup; skins customize banner colors, spinner faces/verbs/wings, tool prefix, response box, branding text
- `process_command()` is a method on `HermesCLI` — dispatches on canonical command name resolved via `resolve_command()` from the central registry - `process_command()` is a method on `HermesCLI` — dispatches on canonical command name resolved via `resolve_command()` from the central registry
- Skill slash commands: `agent/skill_commands.py` scans `~/.hermes/skills/`, injects as **user message** (not system prompt) to preserve prompt caching - Skill slash commands: `hermes_agent/agent/skill_commands.py` scans `~/.hermes/skills/`, injects as **user message** (not system prompt) to preserve prompt caching
### Slash Command Registry (`hermes_cli/commands.py`) ### Slash Command Registry (`hermes_cli/commands.py`)
@ -272,7 +263,7 @@ registry.register(
**2. Add to `toolsets.py`** — either `_HERMES_CORE_TOOLS` (all platforms) or a new toolset. **2. Add to `toolsets.py`** — either `_HERMES_CORE_TOOLS` (all platforms) or a new toolset.
Auto-discovery: any `tools/*.py` file with a top-level `registry.register()` call is imported automatically — no manual import list to maintain. Auto-discovery: any `hermes_agent/tools/*.py` file with a top-level `registry.register()` call is imported automatically — no manual import list to maintain.
The registry handles schema collection, dispatch, availability checking, and error wrapping. All handlers MUST return a JSON string. The registry handles schema collection, dispatch, availability checking, and error wrapping. All handlers MUST return a JSON string.
@ -498,11 +489,11 @@ Rendering bugs in tmux/iTerm2 — ghosting on scroll. Use `curses` (stdlib) inst
### DO NOT use `\033[K` (ANSI erase-to-EOL) in spinner/display code ### DO NOT use `\033[K` (ANSI erase-to-EOL) in spinner/display code
Leaks as literal `?[K` text under `prompt_toolkit`'s `patch_stdout`. Use space-padding: `f"\r{line}{' ' * pad}"`. Leaks as literal `?[K` text under `prompt_toolkit`'s `patch_stdout`. Use space-padding: `f"\r{line}{' ' * pad}"`.
### `_last_resolved_tool_names` is a process-global in `model_tools.py` ### `_last_resolved_tool_names` is a process-global in `hermes_agent/tools/dispatch.py`
`_run_single_child()` in `delegate_tool.py` saves and restores this global around subagent execution. If you add new code that reads this global, be aware it may be temporarily stale during child agent runs. `_run_single_child()` in `delegate_tool.py` saves and restores this global around subagent execution. If you add new code that reads this global, be aware it may be temporarily stale during child agent runs.
### DO NOT hardcode cross-tool references in schema descriptions ### DO NOT hardcode cross-tool references in schema descriptions
Tool schema descriptions must not mention tools from other toolsets by name (e.g., `browser_navigate` saying "prefer web_search"). Those tools may be unavailable (missing API keys, disabled toolset), causing the model to hallucinate calls to non-existent tools. If a cross-reference is needed, add it dynamically in `get_tool_definitions()` in `model_tools.py` — see the `browser_navigate` / `execute_code` post-processing blocks for the pattern. Tool schema descriptions must not mention tools from other toolsets by name (e.g., `browser_navigate` saying "prefer web_search"). Those tools may be unavailable (missing API keys, disabled toolset), causing the model to hallucinate calls to non-existent tools. If a cross-reference is needed, add it dynamically in `get_tool_definitions()` in `hermes_agent/tools/dispatch.py` — see the `browser_navigate` / `execute_code` post-processing blocks for the pattern.
### Tests must not write to `~/.hermes/` ### Tests must not write to `~/.hermes/`
The `_isolate_hermes_home` autouse fixture in `tests/conftest.py` redirects `HERMES_HOME` to a temp dir. Never hardcode `~/.hermes/` paths in tests. The `_isolate_hermes_home` autouse fixture in `tests/conftest.py` redirects `HERMES_HOME` to a temp dir. Never hardcode `~/.hermes/` paths in tests.

View file

@ -38,7 +38,7 @@ RUN npm install --prefer-offline --no-audit && \
# .dockerignore excludes node_modules, so the installs above survive. # .dockerignore excludes node_modules, so the installs above survive.
COPY --chown=hermes:hermes . . COPY --chown=hermes:hermes . .
# Build web dashboard (Vite outputs to hermes_cli/web_dist/) # Build web dashboard (Vite outputs to hermes_agent/cli/web_dist/)
RUN cd web && npm run build RUN cd web && npm run build
# ---------- Python virtualenv ---------- # ---------- Python virtualenv ----------
@ -48,7 +48,7 @@ RUN uv venv && \
uv pip install --no-cache-dir -e ".[all]" uv pip install --no-cache-dir -e ".[all]"
# ---------- Runtime ---------- # ---------- Runtime ----------
ENV HERMES_WEB_DIST=/opt/hermes/hermes_cli/web_dist ENV HERMES_WEB_DIST=/opt/hermes/hermes_agent/cli/web_dist
ENV HERMES_HOME=/opt/data ENV HERMES_HOME=/opt/data
VOLUME [ "/opt/data" ] VOLUME [ "/opt/data" ]
ENTRYPOINT [ "/opt/hermes/docker/entrypoint.sh" ] ENTRYPOINT [ "/opt/hermes/docker/entrypoint.sh" ]

View file

@ -1,3 +1,4 @@
graft hermes_agent
graft skills graft skills
graft optional-skills graft optional-skills
global-exclude __pycache__ global-exclude __pycache__

View file

@ -65,7 +65,7 @@ fi
# Sync bundled skills (manifest-based so user edits are preserved) # Sync bundled skills (manifest-based so user edits are preserved)
if [ -d "$INSTALL_DIR/skills" ]; then if [ -d "$INSTALL_DIR/skills" ]; then
python3 "$INSTALL_DIR/tools/skills_sync.py" hermes-skills-sync
fi fi
exec hermes "$@" exec hermes "$@"

View file

@ -117,18 +117,16 @@ all = [
] ]
[project.scripts] [project.scripts]
hermes = "hermes_cli.main:main" hermes = "hermes_agent.cli.main:main"
hermes-agent = "run_agent:main" hermes-agent = "hermes_agent.agent.loop:main"
hermes-acp = "acp_adapter.entry:main" hermes-acp = "hermes_agent.acp.entry:main"
hermes-skills-sync = "hermes_agent.tools.skills.sync:main"
[tool.setuptools]
py-modules = ["run_agent", "model_tools", "toolsets", "toolset_distributions", "cli", "hermes_constants", "hermes_state", "hermes_time", "hermes_logging", "utils"]
[tool.setuptools.package-data] [tool.setuptools.package-data]
hermes_cli = ["web_dist/**/*"] hermes_agent = ["cli/web_dist/**/*"]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]
include = ["agent", "tools", "tools.*", "hermes_cli", "gateway", "gateway.*", "tui_gateway", "tui_gateway.*", "cron", "acp_adapter", "plugins", "plugins.*", "scripts"] include = ["hermes_agent", "hermes_agent.*", "tui_gateway", "tui_gateway.*"]
[tool.pytest.ini_options] [tool.pytest.ini_options]
testpaths = ["tests"] testpaths = ["tests"]

View file

@ -1102,7 +1102,7 @@ SOUL_EOF
# Seed bundled skills into ~/.hermes/skills/ (manifest-based, one-time per skill) # Seed bundled skills into ~/.hermes/skills/ (manifest-based, one-time per skill)
log_info "Syncing bundled skills to ~/.hermes/skills/ ..." log_info "Syncing bundled skills to ~/.hermes/skills/ ..."
if "$INSTALL_DIR/venv/bin/python" "$INSTALL_DIR/tools/skills_sync.py" 2>/dev/null; then if "$INSTALL_DIR/venv/bin/hermes-skills-sync" 2>/dev/null; then
log_success "Skills synced to ~/.hermes/skills/" log_success "Skills synced to ~/.hermes/skills/"
else else
# Fallback: simple directory copy if Python sync fails # Fallback: simple directory copy if Python sync fails

View file

@ -341,7 +341,7 @@ mkdir -p "$HERMES_SKILLS_DIR"
echo "" echo ""
echo "Syncing bundled skills to ~/.hermes/skills/ ..." echo "Syncing bundled skills to ~/.hermes/skills/ ..."
if "$SCRIPT_DIR/venv/bin/python" "$SCRIPT_DIR/tools/skills_sync.py" 2>/dev/null; then if "$SCRIPT_DIR/venv/bin/hermes-skills-sync" 2>/dev/null; then
echo -e "${GREEN}${NC} Skills synced" echo -e "${GREEN}${NC} Skills synced"
else else
# Fallback: copy if sync script fails (missing deps, etc.) # Fallback: copy if sync script fails (missing deps, etc.)

View file

@ -7,7 +7,7 @@ const BACKEND = process.env.HERMES_DASHBOARD_URL ?? "http://127.0.0.1:9119";
/** /**
* In production the Python `hermes dashboard` server injects a one-shot * In production the Python `hermes dashboard` server injects a one-shot
* session token into `index.html` (see `hermes_cli/web_server.py`). The * session token into `index.html` (see `hermes_agent/cli/web_server.py`). The
* Vite dev server serves its own `index.html`, so unless we forward that * Vite dev server serves its own `index.html`, so unless we forward that
* token, every protected `/api/*` call 401s. * token, every protected `/api/*` call 401s.
* *
@ -59,7 +59,7 @@ export default defineConfig({
}, },
}, },
build: { build: {
outDir: "../hermes_cli/web_dist", outDir: "../hermes_agent/cli/web_dist",
emptyOutDir: true, emptyOutDir: true,
}, },
server: { server: {