Skip to content

feat(gateway): add workspace command for messaging sessions - #42577

Open
qWaitCrypto wants to merge 4 commits into
NousResearch:mainfrom
qWaitCrypto:feat/gateway-workspace-command
Open

feat(gateway): add workspace command for messaging sessions#42577
qWaitCrypto wants to merge 4 commits into
NousResearch:mainfrom
qWaitCrypto:feat/gateway-workspace-command

Conversation

@qWaitCrypto

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds a messaging-gateway /workspace command, with /cwd as an alias, so
Telegram/Discord/Slack-style gateway sessions can bind the current chat/thread
to a project directory.

This is the small missing user-facing piece from the gateway workspace design:
main already has the canonical per-session cwd plumbing, but messaging gateway
users did not have a slash command to set it.

Background

Related issue: #37277

Earlier PR: #37275

The use case from #37277 is one gateway bot serving multiple long-lived chats
or threads, where different sessions may be working on different repositories.
The old PR (#37275) implemented a broader workspace stack, but it was closed
because much of that machinery now exists on main:

  • agent/runtime_cwd.py already has session-scoped cwd resolution.
  • gateway/session_context.set_session_vars(..., cwd=...) already forwards cwd
    into the canonical session cwd contextvar.
  • TUI/desktop already expose a cwd-setting path via session.cwd.set.

This PR rebuilds only the worthwhile missing slice against current main: a
messaging gateway command that uses the existing cwd infrastructure instead of
introducing a parallel workspace model.

Changes made

  • Adds /workspace and /cwd to the central slash-command registry as
    gateway-only commands.
  • Implements /workspace handling in the messaging gateway:
    • /workspace or /workspace status shows the effective workspace.
    • /workspace /absolute/path validates and binds the current gateway session
      to that directory.
    • /workspace clear removes the session override and returns to the global
      gateway cwd.
  • Persists the selected cwd through SessionDB.sessions.cwd.
  • Allows SessionDB.update_session_cwd(session_id, "") to clear an existing
    cwd.
  • During gateway message handling, reads the stored session cwd and passes it to
    the existing set_session_vars(cwd=...) seam.
  • Registers the same cwd as a terminal task override for the active session id,
    matching the existing TUI/ACP pattern.
  • Evicts the cached agent and cleans the stale task environment when the
    workspace changes, so future turns rebuild against the selected cwd.
  • Adds English and Chinese gateway messages.
  • Adds focused tests for command registration, set/clear validation,
    persistence, runtime cwd injection, and clearing stored cwd.

Scope

This PR intentionally does not reimplement the old workspace machinery from
#37275. It does not add a new workspace_cwd field, a separate workspace store,
or a parallel cwd resolver.

The implementation is a thin command layer over existing main-branch plumbing:

/workspace command
  -> SessionDB.sessions.cwd
  -> gateway _set_session_env()
  -> set_session_vars(cwd=...)
  -> agent/runtime_cwd.py session cwd contextvar

Terminal cwd is also registered under the current session_id, because the
gateway agent run passes that id as the tool task_id.

How to test

Focused syntax/check validation run locally:

python -m py_compile \
  hermes_cli/commands.py \
  gateway/slash_commands.py \
  gateway/run.py \
  hermes_state.py \
  tests/gateway/test_workspace_command.py \
  tests/gateway/test_session_env.py \
  tests/test_hermes_state.py

git diff --check -- \
  gateway/run.py \
  gateway/slash_commands.py \
  hermes_cli/commands.py \
  hermes_state.py \
  locales/en.yaml \
  locales/zh.yaml \
  tests/gateway/test_workspace_command.py \
  tests/gateway/test_session_env.py \
  tests/test_hermes_state.py

Both passed.

I did not run the full pytest suite in this checkout because the local virtual
environment does not have pytest installed.

Manual behavior to verify in a messaging gateway:

  1. Run /workspace /absolute/path/to/project.
  2. Send a new prompt and verify the agent sees that directory as its current
    project cwd.
  3. Run /workspace or /cwd to show the bound workspace.
  4. Run /workspace clear and verify the session returns to the global gateway
    cwd.

Checklist

Code

  • My PR contains only changes related to this feature.
  • I reused the existing per-session cwd mechanism on main.
  • I avoided reintroducing the old parallel workspace implementation from
    feat(gateway): add session workspace binding #37275.
  • I added focused tests for the new command and session cwd handoff.
  • I ran the full test suite.

Documentation & Housekeeping

  • I added user-facing English and Chinese gateway messages.
  • No config keys or dependencies were added.
  • No installer, desktop, TUI, or tool behavior was changed beyond the
    messaging gateway command path.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/gateway Gateway runner, session dispatch, delivery comp/cli CLI entry point, hermes_cli/, setup wizard labels Jun 9, 2026
@qWaitCrypto
qWaitCrypto force-pushed the feat/gateway-workspace-command branch from 3f76553 to 28a6d2e Compare June 9, 2026 05:48
@liuhao1024

Copy link
Copy Markdown
Contributor

Code Review Summary

Verdict: Clean — no issues found

Overview

This PR adds a /workspace gateway command that binds a chat session to a project directory (cwd). The per-session cwd is persisted in the session DB, propagated across session-ID rotation (compression), and injected into the terminal/file-tool cwd resolution chain so that gateway chats get project-local file operations without a prior cd.

Key design decisions

  • Shared env isolation: CWD-only task overrides correctly avoid mutating the collapsed shared "default" environment, preventing workspace leakage across concurrent gateway sessions.
  • Session-ID carry: _carry_gateway_session_cwd migrates the registered cwd when compression rotates the session ID — good defensive design.
  • Resolution priority: live terminal cwd → task/session override → TERMINAL_CWD → process cwd — well-ordered precedence chain.

Quality

  • 144-line dedicated test file (test_workspace_command.py) covering set/clear, session-id carry, relative-path rejection, missing-path rejection
  • Additional test in test_session_env.py verifying cwd propagation through _set_session_env
  • Session-scoped CWD isolation test in test_terminal_task_cwd.py preventing leakage through shared default env

No concerns

  • No security issues (absolute-path requirement, is_dir check, PROFILE_NAME_RE validation for profile-delete path)
  • No race conditions (all cwd state is per-session, shared env correctly isolated)
  • Gateway command registration follows established pattern (commands.pyslash_commands.pyrun.py dispatch)

Reviewed by Hermes Agent (cron code-review-lite)

@qWaitCrypto

Copy link
Copy Markdown
Contributor Author

In this pr, hermes showed stronger debugging capabilities than codex and claudecode.😂

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for narrowing this to the existing per-session cwd seam. The missing gateway command is still a valid gap: current main's registry/dispatch has no /workspace or /cwd (hermes_cli/commands.py:123-126, gateway/run.py:9876-9880), while set_session_vars(..., cwd=...) already supports the intended runtime handoff (gateway/session_context.py:157-214).

Problems

  • The submitted handler uses synchronous self.session_store and self._session_db calls. Current main requires the async facades: AsyncSessionStore offloads store I/O (gateway/session.py:969-983) and GatewayRunner creates _session_db as AsyncSessionDB (gateway/run.py:3040-3044). Its forwarded get_session/update_session_cwd calls are coroutines (hermes_state.py:6691-6704), so the submitted direct calls will not persist cwd.
  • Clearing only cwd is incomplete for current workspace grouping: workspace_key() prefers git_repo_root (hermes_state.py:35-49).

Suggested changes

  • Port the command to await self.async_session_store and await self._session_db, then implement an atomic clear for cwd plus stale git metadata.
  • Rebuild the terminal/file-tool portion from current main rather than applying the stale snapshots.

Automated hermes-sweeper review.

Comment thread gateway/slash_commands.py

return t("gateway.set_home.success", name=chat_name, chat_id=chat_id)

async def _handle_workspace_command(self, event: MessageEvent) -> str:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current gateway handlers must use await self.async_session_store.get_or_create_session(source). session_store is synchronous, and the async facade exists to keep its I/O off the event loop (gateway/session.py:969-983).

Comment thread gateway/slash_commands.py
@@ -47,6 +47,86 @@
class GatewaySlashCommandsMixin:
"""In-session slash-command handlers for GatewayRunner."""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

self._session_db is now an AsyncSessionDB; get_session() returns a coroutine and this helper will catch the resulting attribute error and return an empty cwd. Make this helper async and await the DB call.

Comment thread gateway/slash_commands.py
source=self._workspace_source_label(session_entry.session_id),
global_cwd=global_cwd,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This write is un-awaited under current main's AsyncSessionDB, so /workspace clear reports success without persisting. Use an awaited clear operation that also handles stale git_branch/git_repo_root metadata.

@teknium1 teknium1 added sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit area/sessions Session lifecycle, resume, persistence, history labels Jul 14, 2026

@GottZ GottZ left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was generated by AI during triage.

Summary

Two PRs address the missing messaging-gateway workspace control through different models: #42577 binds an absolute per-session cwd using the existing session/runtime cwd plumbing, while #62075 adds a named workspace registry with create, list, switch, and remove operations. Both cover the core /workspace command, but each diff currently conflicts with current-main APIs or cwd semantics identified in contributor reviews.

Related pull requests

  • #42577 related — (+584/-36) — author action required: #42577 is the narrower implementation, adding /workspace and /cwd, persisted session cwd, session-ID carry, runtime/tool propagation, and isolation tests. Despite the keep_open review on #42577, its diff calls the now-async session store and DB facades synchronously, so cwd persistence and carry cannot work as submitted; it also clears cwd without addressing the git_repo_root precedence documented in that review.
  • #62075 duplicate — (+276/-2) — salvageable broader alternative: #62075 adds a persistent named-workspace registry, per-session selection, routing-state persistence, documentation, and tests. The keep_open review on #62075 identifies blocking diff-level issues: adding env_type turns a CWD-only override into an isolation signal, host paths are not mapped for container backends, and the branch's session-store calls do not match current main, including the concurrency state referenced by commit b3f77f5.

Duplicates

#42577 and #62075 overlap on registering and dispatching a messaging-gateway /workspace command and applying a per-session cwd, but they are not complete duplicates: #62075 additionally implements named workspace registration and lifecycle management, whereas #42577 focuses on direct absolute-path binding and deeper terminal/file-tool cwd isolation.

Suggested consolidation

Author action: rebase #42577 onto main and adapt its focused command to the async session/DB facades, including correct clearing of both cwd and the higher-priority workspace grouping state; alternatively, split out that corrected command and its isolation tests as the salvageable core. Keep #62075 open only with a salvage path for its named registry, routing-state persistence, documentation, and non-destructive removal behavior after removing the unintended env_type isolation and defining tested host-to-container path mapping. Do not close either as a duplicate yet because their retained scopes differ, and do not merge either as submitted because the blocking concerns in both keep_open reviews remain visible in the diffs.

Complex graph

flowchart LR
    classDef open fill:#dbeafe,stroke:#1d4ed8,color:#1e3a8a
    classDef merged fill:#dcfce7,stroke:#15803d,color:#14532d
    classDef closed fill:#e5e7eb,stroke:#6b7280,color:#1f2937
    classDef unverified fill:#f3f4f6,stroke:#9ca3af,color:#374151
    classDef best stroke-width:3px,stroke:#b45309
    classDef target stroke-width:3px,stroke:#4338ca
    subgraph Dup42577 ["PRs duplicating each other"]
        P42577["PR #42577 (open)"]
        P62075["PR #62075 (open)"]
    end
    class P42577 open
    class P62075 open
    class P42577 target
    click P42577 "https://github.com/NousResearch/hermes-agent/pull/42577"
    click P62075 "https://github.com/NousResearch/hermes-agent/pull/62075"
Loading

Graph: solid arrow = fixes / best fix, dashed arrow = partial or unverified (see edge label); boxed group = PRs duplicating each other; amber border = best fix; indigo border = target; gray node = closed (state tag in the node label).

Cross-PR triage: Reviewed 2 pull requests and 0 issues in this complex. Each diff was read against this issue; Assessment working set: 58 kB of PR diffs, 7 kB of issue/PR text, 4 kB of discussion (4 comments), 0 verify verdicts. verdicts reflect diff content, not PR titles. Part of an automated triage batch.

Resolve async session persistence, atomic workspace metadata updates, and current terminal cwd integration from review feedback.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-platform-windows Sweeper risk: may break or behave differently on native Windows sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants