Overview
Restructure the hermes-agent codebase from a flat collection of top-level modules and independent packages into a single hermes_agent/ package with clear subpackages, dependency direction, and import hygiene.
No logic changes. No API changes. No behavior changes. The same code runs the same way — it just lives at better addresses.
Approach: Top-down layered. Move packages with the fewest inbound dependencies first, working inward toward the core. Each PR moves one layer, leaves the codebase fully working. No shims, no migration scripts, no deferred breakage.
Manifest: hermes_restructure_manifest_v3.md (in repo root, untracked)
Why now
Problem 1: There's no package. The pyproject.toml tells the story — 11 loose py-modules at the root, 8 independent packages discovered via packages.find, and entry points pointing at three different top-level locations. There is no hermes_agent you can import.
Problem 2: Related code is scattered. Model adapters live in agent/, transports live in agent/transports/, execution backends are nested under tools/environments/. The CLI is split between an 11k-line cli.py and a 51-file hermes_cli/ package.
Problem 3: The cost of waiting compounds. Every PR merged against the current structure deepens existing patterns. Before v1 is the natural moment.
Target structure
hermes_agent/
├── constants.py, state.py, logging.py, time.py, utils.py # leaf modules
├── agent/ # core agent loop, prompt builder, context, memory
├── providers/ # transport + adapter per provider, credentials, pricing
├── backends/ # execution environments (local, docker, ssh, modal, ...)
├── tools/ # tool registry, dispatch, all tool implementations
│ ├── browser/, mcp/, skills/, media/, files/, security/
├── cli/ # CLI entry point, REPL, commands, auth, models, UI
│ ├── auth/, models/, ui/
├── gateway/ # gateway runner, session dispatch, platforms
├── acp/ # ACP adapter
├── cron/ # cron scheduler
└── plugins/ # bundled plugins (memory, context_engine)
Dependency DAG (post-restructure)
hermes_agent.cli
↓
hermes_agent.agent ← hermes_agent.gateway ← hermes_agent.acp
↓ ↘
hermes_agent.providers hermes_agent.tools
↓
hermes_agent.backends
hermes_agent.constants, .state, .logging, .time ← imported by everything
PR Tracker
| PR |
Sub-issue |
Status |
What moves |
Files |
Blast radius |
| PR 1 |
#14586 |
📋 Planned |
acp_adapter/ → hermes_agent/acp/ |
9 |
1 prod + 11 test refs |
| PR 2 |
#14587 |
📋 Planned |
cron/ → hermes_agent/cron/ |
3 |
15 prod + 81 test refs |
| PR 3 |
#14588 |
📋 Planned |
gateway/ → hermes_agent/gateway/ |
~48 |
68 prod + 1,224 test refs |
| PR 4 |
#14589 |
📋 Planned |
hermes_cli/ + plugins/ → hermes_agent/cli/ + hermes_agent/plugins/ |
~72 |
319 prod + 990 test refs |
| PR 5 |
#14590 |
📋 Planned |
tools/ + top-level modules → hermes_agent/tools/ + hermes_agent/backends/ |
~88 |
318 prod + 1,088 test refs |
| PR 6 |
#14591 |
📋 Planned |
agent/ + leaf modules → hermes_agent/agent/ + hermes_agent/providers/ |
~59 |
595 prod + ~950 test refs |
Dependency graph
PR1 (#14586) → PR2 (#14587) → PR3 (#14588) → PR4 (#14589) → PR5 (#14590) → PR6 (#14591)
(strictly sequential — each PR rewrites imports that depend on prior moves)
Abort points
Each PR delivers standalone value. Safe stopping points:
- After PR 1 —
hermes_agent/ package exists, canary PR proves the workflow
- After PR 3 — all "peripheral" packages moved (acp, cron, gateway)
- After PR 6 — single package, clean imports, full restructure complete
What each PR does NOT do
Every PR is purely mechanical. Across all PRs:
- No logic changes — code behavior is identical before and after
- No layer inversion fixes —
agent/ importing from cli/ is moved as-is
- No API changes — CLI commands, entry points, config format unchanged
- No migration script — top-down approach means each PR is atomic
Commit strategy (same for all PRs)
| Commit |
What |
Why |
| Commit 1 |
Pure git mv — files move, zero content changes |
100% similarity for clean blame |
| Commit 2 |
Import rewrite — all 6 categories (import, from-import, patch(), monkeypatch, sys.modules, importlib) |
Content changes only in import/string lines |
| Commit 3 |
Infra + fixups — pyproject.toml, shell scripts, __init__.py re-exports, .git-blame-ignore-revs |
Build system points at new locations |
Verification checklist (run after every PR)
python -c "import hermes_agent"
rg "^from <old_package>[\. ]" --type py # adjust pattern per PR
rg "(import_module|__import__|importlib)\(.*['\"]" --type py
pytest
hermes --help && hermes-agent --help
Key decisions
| Decision |
Choice |
Rationale |
| Move order |
Top-down (least-imported first) |
Each file's imports rewritten at most once per moved dependency |
| plugins/ |
Moves into hermes_agent/plugins/ |
Single package boundary |
__init__.py re-exports |
Ship in same PR that creates sub-packages |
Zero breakage between PRs |
| sys.path hacks |
Strip from production code |
Redundant with editable install |
| Process-name matching |
Keep old patterns for one release cycle |
Backwards compat for running gateways |
| Migration script |
Not needed |
Top-down approach is atomic per PR |
Learnings from V1 prototype
The monolithic approach (branch sid/foundational-restructure, draft PR #14498) validated the mechanics:
- Tests are ~75% of the diff (each test averages ~15 rewrite sites)
git mv in a separate commit gets 100% similarity — clean blame
- Non-
.py files (scripts/hermes-gateway, install.ps1) get missed by **/*.py globs
- 4 test files write old-style imports as string literals into dynamically-created plugin files
- Plugin
sys.modules surgery needs dedicated verification (string-based paths)
_get_bundled_dir() in skills_sync breaks silently after move (wrong Path(__file__) depth)
Superseded
Reference
Follow-up issues
| Issue |
Priority |
What |
| #14278 |
Medium |
Fix agent→cli layer inversions in providers/ |
| #14279 |
Low |
Remove legacy process-name patterns after v1 |
Overview
Restructure the hermes-agent codebase from a flat collection of top-level modules and independent packages into a single
hermes_agent/package with clear subpackages, dependency direction, and import hygiene.No logic changes. No API changes. No behavior changes. The same code runs the same way — it just lives at better addresses.
Approach: Top-down layered. Move packages with the fewest inbound dependencies first, working inward toward the core. Each PR moves one layer, leaves the codebase fully working. No shims, no migration scripts, no deferred breakage.
Manifest:
hermes_restructure_manifest_v3.md(in repo root, untracked)Why now
Problem 1: There's no package. The pyproject.toml tells the story — 11 loose
py-modulesat the root, 8 independent packages discovered viapackages.find, and entry points pointing at three different top-level locations. There is nohermes_agentyou can import.Problem 2: Related code is scattered. Model adapters live in
agent/, transports live inagent/transports/, execution backends are nested undertools/environments/. The CLI is split between an 11k-linecli.pyand a 51-filehermes_cli/package.Problem 3: The cost of waiting compounds. Every PR merged against the current structure deepens existing patterns. Before v1 is the natural moment.
Target structure
Dependency DAG (post-restructure)
PR Tracker
acp_adapter/→hermes_agent/acp/cron/→hermes_agent/cron/gateway/→hermes_agent/gateway/hermes_cli/+plugins/→hermes_agent/cli/+hermes_agent/plugins/tools/+ top-level modules →hermes_agent/tools/+hermes_agent/backends/agent/+ leaf modules →hermes_agent/agent/+hermes_agent/providers/Dependency graph
Abort points
Each PR delivers standalone value. Safe stopping points:
hermes_agent/package exists, canary PR proves the workflowWhat each PR does NOT do
Every PR is purely mechanical. Across all PRs:
agent/importing fromcli/is moved as-isCommit strategy (same for all PRs)
git mv— files move, zero content changes__init__.pyre-exports,.git-blame-ignore-revsVerification checklist (run after every PR)
Key decisions
hermes_agent/plugins/__init__.pyre-exportsLearnings from V1 prototype
The monolithic approach (branch
sid/foundational-restructure, draft PR #14498) validated the mechanics:git mvin a separate commit gets 100% similarity — clean blame.pyfiles (scripts/hermes-gateway,install.ps1) get missed by**/*.pyglobssys.modulessurgery needs dedicated verification (string-based paths)_get_bundled_dir()in skills_sync breaks silently after move (wrongPath(__file__)depth)Superseded
Reference
hermes_restructure_manifest_v3.md(in repo root)docs/restructure-v1-reference/Follow-up issues