fix(acp): bind session_id in session context for subprocess isolation

The ACP prompt path called set_session_vars(session_key=session_id, ...)
without passing session_id, so the HERMES_SESSION_ID ContextVar was bound
to its explicit "" default. Once the session-context machinery is engaged,
_inject_session_context_env treats an explicitly-bound "" as authoritative
and writes it to the child env — so subprocesses spawned during an ACP turn
got an empty HERMES_SESSION_ID instead of the session's own id.

Pass session_id through so child subprocesses carry the correct id.

Salvage of the ACP half of #53454 by @necoweb3 (the V4A-path half is
salvaged separately in the file-tools PR).

Co-authored-by: necoweb3 <sswdarius@gmail.com>
This commit is contained in:
dsad 2026-08-01 13:37:52 -07:00 committed by Teknium
parent ff6ed7c491
commit a5f94e93ad
2 changed files with 38 additions and 1 deletions

View File

@ -1868,7 +1868,7 @@ class HermesACPAgent(acp.Agent):
# model emits absolute paths under ~/.hermes/workspace and the
# edit silently lands outside the editor's workspace.
session_tokens = set_session_vars(
session_key=session_id, cwd=state.cwd,
session_key=session_id, session_id=session_id, cwd=state.cwd,
)
except Exception:
session_tokens = None

View File

@ -409,6 +409,43 @@ class TestPrompt:
assert isinstance(resp, PromptResponse)
assert resp.stop_reason == "refusal"
@pytest.mark.asyncio
async def test_prompt_binds_session_id_into_subprocess_env(self, agent, mock_manager):
"""The ACP prompt path must bridge the session id into child subprocesses.
Regression: ``set_session_vars`` was called with ``session_key`` only,
leaving the ``HERMES_SESSION_ID`` ContextVar bound to the explicit ""
default. Once the session-context machinery is engaged, that empty value
is authoritative so ``_make_run_env`` handed child subprocesses an
empty ``HERMES_SESSION_ID`` instead of the session's own id.
"""
from tools.environments.local import _make_run_env
resp = await agent.new_session(cwd=".")
state = mock_manager.get_session(resp.session_id)
captured: dict[str, str | None] = {}
def _run(*args, **kwargs):
# Runs inside the session context copy set up by prompt().
captured["child"] = _make_run_env({}).get("HERMES_SESSION_ID")
return {"final_response": "ok", "messages": []}
state.agent.run_conversation = _run
state.agent.model = "test-model"
state.agent.provider = "openrouter"
mock_conn = MagicMock(spec=acp.Client)
mock_conn.session_update = AsyncMock()
agent._conn = mock_conn
await agent.prompt(
prompt=[TextContentBlock(type="text", text="hi")],
session_id=resp.session_id,
)
assert captured.get("child") == resp.session_id