Skip to content

feat(cli): add /set-workspace command to change session cwd#23535

Open
xiaoquisme wants to merge 1 commit into
NousResearch:mainfrom
xiaoquisme:feat/cli-set-workspace-command
Open

feat(cli): add /set-workspace command to change session cwd#23535
xiaoquisme wants to merge 1 commit into
NousResearch:mainfrom
xiaoquisme:feat/cli-set-workspace-command

Conversation

@xiaoquisme

@xiaoquisme xiaoquisme commented May 11, 2026

Copy link
Copy Markdown

Summary

Adds a /set-workspace slash command to change the working directory used by the terminal, file, and code-execution tools for the current CLI session. Aliases: /workspace, /cd, /setworkspace.

The change takes effect immediately — no /reset required — because all downstream caches that read cwd are kept in sync.

Why

Switching projects mid-session currently means either restarting Hermes (losing context) or relying on cd inside a terminal call (only updates the active terminal env, not file_tools' relative-path anchor or new sandboxes). /set-workspace makes this a first-class operation.

Behavior

/set-workspace                       # show current workspace + usage
/set-workspace ~/personal/foo        # absolute, with ~ expansion
/cd ../bar                           # relative, anchored on current TERMINAL_CWD
/workspace "/path with spaces/"      # quoted (single or double)
/set-workspace $MY_PROJECT           # env var expansion

Implementation

Updates the single source of truth (TERMINAL_CWD env var) plus all downstream caches that read from it on tool invocation:

  1. os.environ["TERMINAL_CWD"] — read by every tool path that resolves cwd: file_tools relative-path anchoring, code_execution sandbox cwd, runtime footer, checkpoint manager, subagent inheritance via delegate_tool.
  2. tools.terminal_tool._active_environments[*].cwd — the cwd actually fed to subprocesses for every long-lived shell or sandbox. Normally maintained by the cwd-marker mechanism on each cd; force-synced here because the user is changing it out-of-band.
  3. tools.file_tools._file_ops_cache[*].cwdShellFileOperations prefers env.cwd (already updated above) and falls back to self.cwd, so we keep both in sync.

Session-scoped only; does not write to config.yaml. Failures during cache sync (e.g. a remote backend that refuses cwd reassignment) are isolated per-cache so they cannot block the env var update or other downstream caches.

Validation: rejects nonexistent paths, files, and directories without read+exec permission. Same-dir detected and reported as no-op.

Related Issues

This PR doesn't directly close any of these, but /set-workspace provides a manual escape hatch / mitigation for several long-standing pain points around session cwd handling:

Test Plan

tests/cli/test_cli_set_workspace.py — 15 tests, all pass:

  • Arg parsing: no-arg shows current + usage hint (no mutation), blank arg same, single/double quote stripping
  • Path resolution: ~ expansion, $VAR expansion, relative paths anchored on TERMINAL_CWD not os.getcwd (load-bearing invariant — explicitly tested by moving the process cwd elsewhere)
  • Validation: nonexistent rejected, file rejected, unreadable dir rejected (skipped under root), same-dir no-op
  • Cache sync: active terminal envs synced, file_ops synced, partial sync failure isolated (one bad env doesn't block env var update or file_ops update)
  • Registry wiring: all 4 aliases (set-workspace, setworkspace, workspace, cd) resolve to canonical name with cli_only=True

Full local regression: tests/cli/ + tests/hermes_cli/test_commands.py812 passed (8 unrelated ruamel-missing tests deselected, exists on main independent of this PR).

Files Changed

  • hermes_cli/commands.py — register CommandDef (1 entry, 3 aliases)
  • cli.py — add _handle_set_workspace_command (~95 lines including docstring + per-cache try/except blocks) and one-line dispatch in process_command
  • tests/cli/test_cli_set_workspace.py — new, 15 tests

Notes

  • For non-local terminal backends (docker/ssh/modal/etc.), TERMINAL_CWD still updates so file_tools relative-path resolution moves, but the recommendation in the docstring is to do cd via the terminal tool inside the remote session for shell-state correctness.
  • Pattern mirrors how cron/scheduler.py already uses TERMINAL_CWD to bridge per-job workdir into the agent process.

Adds a /set-workspace slash command (with aliases /workspace, /cd,
/setworkspace) that changes the working directory used by the
terminal, file, and code-execution tools for the current session.

Effect is immediate -- no /reset required -- because the implementation
updates the single source of truth (TERMINAL_CWD env var) plus all
downstream caches that read from it on tool invocation:

1. os.environ[TERMINAL_CWD]: read by every tool path that resolves
   cwd, including file_tools relative-path anchoring, code_execution
   sandbox cwd, runtime footer, checkpoint manager, subagent
   inheritance.
2. tools.terminal_tool._active_environments[*].cwd: the cwd actually
   fed to subprocess for every long-lived shell or sandbox. Normally
   maintained by the cwd-marker mechanism on each cd, force-synced
   here because the user is changing it out-of-band.
3. tools.file_tools._file_ops_cache[*].cwd: ShellFileOperations
   prefers env.cwd (already updated above) and falls back to
   self.cwd, so we keep both in sync.

The change is session-scoped and does NOT write to config.yaml.
Path arguments support ~ expansion, $VAR expansion, single/double
quotes (for paths with spaces), and relative paths (anchored on the
current TERMINAL_CWD, not os.getcwd, so chained /cd subdir works
intuitively from the new workspace). Validation rejects nonexistent
paths, files, and directories without read+exec permission.

Failures during cache sync (e.g. a remote backend that refuses cwd
reassignment) are swallowed per-cache so they cannot block the env
var update or other downstream caches.

Tests: tests/cli/test_cli_set_workspace.py covers arg parsing
(no-arg/blank/quoted), path resolution (~ / $VAR / relative anchor
on TERMINAL_CWD), validation (nonexistent / file / unreadable /
no-op same-dir), cache sync (terminal envs, file_ops, partial
failure isolation), and registry wiring (all 4 aliases resolve to
the canonical name with cli_only=True). 15 tests, all pass.

Files:
- hermes_cli/commands.py: register CommandDef
- cli.py: add _handle_set_workspace_command and dispatch in
  process_command
- tests/cli/test_cli_set_workspace.py: new
@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/cli CLI entry point, hermes_cli/, setup wizard tool/terminal Terminal execution and process management tool/file File tools (read, write, patch, search) labels May 11, 2026
@pmos69

pmos69 commented May 20, 2026

Copy link
Copy Markdown

Heads-up on a related gap — I've opened #29531 for per-session working directories on gateway / OpenAI-compatible API sessions. /set-workspace here is correctly cli_only=True and mutates the global TERMINAL_CWD, which is the right scope for an interactive CLI session — so it's orthogonal to that issue, and nothing needs to change on this PR.

The open design question in #29531 is whether a gateway-routable, per-session cwd should reuse a command like this one or be a separate mechanism. Linking it here so the author and maintainers can weigh in if /set-workspace's shape is relevant to that decision.

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

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P3 Low — cosmetic, nice to have 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