Skip to content

feat(tools): add named and dynamic execution targets - #80275

Open
ribaricplusplus wants to merge 1 commit into
NousResearch:mainfrom
crosslink-ch:feat/named-execution-targets-2
Open

ribaricplusplus wants to merge 1 commit into
NousResearch:mainfrom
crosslink-ch:feat/named-execution-targets-2

Conversation

@ribaricplusplus

@ribaricplusplus ribaricplusplus commented Aug 6, 2026

Copy link
Copy Markdown

What does this PR do?

Adds named and runtime-registerable execution targets so one Hermes session can route work across local, SSH, Docker, and other configured terminal backends without changing its global backend between calls.

The selected target and immutable runtime generation propagate through terminal commands, file operations, background processes, approvals, delegation, and nested execute_code RPC calls. Runtime targets can be registered, replaced, drained, and removed through hermes targets without restarting an already-activated gateway.

This is the deliberate, cleaned-up replacement for #67076. It contains one feature commit on pinned upstream base cf64ca20c5ab99ebf7e8ca272c69edc7ea0636ed and no unrelated fork commits. The mature feature delta was ported from crosslink-ch#2 and reconciled against the current upstream behavior on that pinned base.

Related to #1855.

Supersedes #67076.

Type of Change

  • 🐛 Bug fix (non-breaking change)
  • ✨ New feature
  • 🔒 Security hardening
  • 📝 Documentation update
  • ✅ Tests
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill

Changes Made

  • Add terminal.default_target and a fixed-schema terminal.targets map while preserving legacy single-backend configuration.
  • Standardize the model-facing routing selector as execution_target across terminal, file tools, execute_code, and related wrappers. The unpublished target routing alias is not accepted.
  • Bind the dispatch-time execution_target to the value evaluated by hooks, guardrails, and approvals. Execution middleware may still rewrite ordinary arguments, but cannot replace, inject, remove, or mutate the routing selector after authorization.
  • Preserve search_files.target exclusively as the content / files search-mode selector; search_files.execution_target selects the backend.
  • Add a profile-scoped runtime target registry and lifecycle CLI: hermes targets register, list, show, drain, and remove.
  • Make runtime registration visible to an already-running gateway on the next dispatch while keeping tool schemas and cached prompts stable.
  • Freeze one target snapshot per dispatch so authorization, approval, checkpoints, backend construction, execution, nested RPC, and result metadata use the same generation.
  • Scope cwd, process sessions, approvals, file state, tool-result persistence, and profile-multiplexed state by immutable runtime identity.
  • Add provider ownership, generation compare-and-swap, draining, stale teardown protection, collision isolation, bounded registry reads, atomic publication, and secret-safe JSON output.
  • Preserve producing-runtime metadata on background-process error paths.

Configuration example

terminal:
  default_target: local
  timeout: 180
  targets:
    local:
      backend: local
      cwd: /home/me/project
    compute:
      backend: ssh
      ssh_host: compute.example.com
      ssh_port: 22
      ssh_user: agent
      ssh_key: ~/.ssh/compute_ed25519
      cwd: ~/project
terminal(command="pytest -q", execution_target="compute")
write_file(path="notes.txt", content="remote\n", execution_target="compute")
search_files(pattern="TODO", target="content", execution_target="compute")
execute_code(code="...", execution_target="compute")

Runtime registration example:

hermes targets register compute \
  --backend ssh \
  --host compute.example.com \
  --user agent \
  --cwd /workspace \
  --provider my-controller \
  --owner-id compute-17 \
  --generation server-17 \
  --json

How to Test

Focused regression gate:

scripts/run_tests.sh \
  tests/tools/test_execution_targets.py \
  tests/tools/test_named_execution_target_routing.py \
  tests/tools/test_code_execution_named_target.py \
  tests/tools/test_process_target_routing.py \
  tests/tools/test_runtime_execution_target_registry.py \
  tests/tools/test_runtime_execution_target_locking.py \
  tests/tools/test_runtime_execution_target_background.py \
  tests/hermes_cli/test_targets_cli.py

Manual route check:

  1. Configure one local target and one SSH target.
  2. Start Hermes and write the same relative path on each target.
  3. Read both files back and confirm the target-specific markers differ.
  4. Change cwd on one target and confirm the other target's cwd is unchanged.
  5. Start and inspect a background process on the SSH target.
  6. Run nested tool calls through execute_code and confirm they inherit the selected target.
  7. Register a new runtime target from another process and confirm the existing process can use it without a restart.
  8. Drain/remove the target and confirm new calls fail while existing background work remains cleanable by immutable runtime identity.

Author Verification

Verification was performed on exact head f4fc3be5d1049659c86a760026a48a1243461832 against pinned base cf64ca20c5ab99ebf7e8ca272c69edc7ea0636ed on a task-owned Hetzner devbox:

  • Changed/affected test matrix: 1,155 passed, 0 failed, 11 Windows-only skips across 35 files.
  • Canonical full Python suite: 34,494 passed, 3 failed, 308 skipped; all 3 failures reproduce identically on the pinned upstream base in untouched files, so 0 candidate-only regressions.
  • Ruff lint, compile, lock, diff, and conflict-marker gates: passed (whole-repository Ruff lint; 66 changed Python files parsed/compiled; lock, diff, conflict-marker, and public-selector audits).
  • Production documentation build: passed (npm ci && npm run build with CI-required npm 12 from the frozen archive).
  • Same-process runtime registration/schema-stability probe: passed (same-process CLI registration became visible without restart; schemas remained byte-stable; removed alias rejected).
  • Authorization-binding exploit probe: passed (29 middleware/dispatch tests; post-authorization alpha-to-beta retargeting rejected before registry dispatch).
  • Real target-routing acceptance: passed (real loopback SSH terminal and file round-trip, nested execute_code inheritance, drain/removal, and producing-runtime cleanup).
  • Independent frozen-candidate integration/security review: PASS; no blocking findings; frozen provenance, 71-path reconciliation, upstream-overlap seams, selector contract, authorization binding, runtime lifecycle, nested RPC confinement, and background-process metadata independently reverified; no files modified.

The three full-suite failures are two tests/test_mcp_serve.py EventBridge timing/mtime assertions and one tests/test_hermes_state.py FTS query-trace assertion. Both files are unchanged by this PR, and all three failures reproduce identically on pristine pinned base cf64ca20c5ab99ebf7e8ca272c69edc7ea0636ed under the same locked CI environment.

Interlock with #82243

#82243 proposes making all internal tools available to execute_code rather than only the current bridge subset. This PR remains compatible with the current subset-based bridge. If #82243 lands first, resolve the overlap by preserving its full availability model while retaining this PR's immutable execution_target and runtime-generation binding for nested RPC dispatch.

@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tools Tool registry, model_tools, toolsets tool/terminal Terminal execution and process management tool/file File tools (read, write, patch, search) tool/code-exec execute_code sandbox tool/delegate Subagent delegation backend/docker Docker container execution backend/ssh SSH remote execution area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have needs-decision Awaiting maintainer decision before any implementation sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Aug 6, 2026

Copy link
Copy Markdown
Contributor

Interlock with #82243

#82243 makes the helpers advertised inside execute_code depend on whether the active backend is local: deferred bridges are allowed locally and deliberately stripped on remote/file RPC. This PR allows local and remote named targets to coexist in one session and routes execute_code per call.

That creates a direct architecture edge: bridge schema/authorization can no longer be derived from one process-global TERMINAL_ENV or one fixed default-target schema. When rebasing after #82243, make bridge exposure and runtime authorization target-aware (or keep the fixed schema conservative and provide an explicit target-aware discovery contract), with local-target and remote-target regressions in the same session.

Port the mature named-target implementation from #2.
Standardize model-facing routing on execution_target, add the runtime target registry/lifecycle CLI, and bind routing through authorization and process cleanup.
Source range: 84e7f05..3c08ee8
@ribaricplusplus
ribaricplusplus force-pushed the feat/named-execution-targets-2 branch from b82ad1f to f4fc3be Compare August 17, 2026 15:11
@ribaricplusplus ribaricplusplus changed the title feat(tools): add named execution targets feat(tools): add named and dynamic execution targets Aug 17, 2026
@ribaricplusplus
ribaricplusplus marked this pull request as ready for review August 18, 2026 09:25
@ribaricplusplus

Copy link
Copy Markdown
Author

(Human written comment)

I've been using this PR for a week with my Hermes without issues. I've mostly been using it with Hetzner servers. My Hermes can provision additional Hetzner servers for development, and it used this feature so that it's more convenient for the LLM to work on the server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles backend/docker Docker container execution backend/ssh SSH remote execution comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/tools Tool registry, model_tools, toolsets needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/code-exec execute_code sandbox tool/delegate Subagent delegation tool/file File tools (read, write, patch, search) tool/terminal Terminal execution and process management type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants