fix(tui_gateway): bind the branched agent to the parent profile's home + state.db

session.branch wrote the child ROW into the parent's profile db but
built the live agent with the launch defaults: _make_agent fell back to
_get_db() and no HERMES_HOME override was active. The branched agent's
own message flushes — and any later compression rotation it performed —
therefore landed back on the launch profile, splitting the lineage one
turn after the branch. Mirror session.create/resume: open the parent
profile's SessionDB for the agent and hold the home override across the
build, so config/skills/memory resolve to the profile too.

Spotted in #70605's sibling implementation of the same fix.

Co-authored-by: HexLab98 <liruixinch@outlook.com>
This commit is contained in:
Brooklyn Nicholson 2026-07-24 10:05:05 -05:00
parent e9a243ef78
commit 29dd621498
2 changed files with 47 additions and 21 deletions

View file

@ -9151,11 +9151,12 @@ def test_session_branch_writes_to_parent_profile_db(monkeypatch, tmp_path):
monkeypatch.setattr(server, "_get_db", lambda: LaunchDB())
monkeypatch.setattr("hermes_state.SessionDB", ProfileDB)
monkeypatch.setattr(server, "_claim_active_session_slot", lambda *a, **k: (None, None))
monkeypatch.setattr(
server,
"_make_agent",
lambda *a, **k: FakeAgent(),
)
def _fake_make_agent(*a, **k):
seen["agent_session_db"] = k.get("session_db")
return FakeAgent()
monkeypatch.setattr(server, "_make_agent", _fake_make_agent)
monkeypatch.setattr(server, "_set_session_context", lambda *a, **k: {})
monkeypatch.setattr(server, "_clear_session_context", lambda *a, **k: None)
monkeypatch.setattr(server, "_resolve_model", lambda: "test-model")
@ -9182,6 +9183,10 @@ def test_session_branch_writes_to_parent_profile_db(monkeypatch, tmp_path):
assert seen.get("launch_create") is None
child_sid = resp["result"]["session_id"]
assert server._sessions[child_sid]["profile_home"] == str(profile_home)
# The branched AGENT must be bound to the parent profile's state.db —
# not just the row. Otherwise its own flushes (and a later compression
# rotation) land on the launch db, splitting the lineage again.
assert isinstance(seen.get("agent_session_db"), ProfileDB)
finally:
for k in list(server._sessions):
server._sessions.pop(k, None)

View file

@ -9734,27 +9734,48 @@ def _(rid, params: dict) -> dict:
lease.release()
return _err(rid, 5008, f"branch failed: {e}")
try:
tokens = _set_session_context(new_key)
# Bind the branched AGENT to the parent's profile, mirroring
# session.create/resume: home override so config/skills/memory resolve
# to the profile during the build, and the profile's own state.db
# handle so the live agent's message flushes — and any later
# compression rotation — persist there. Writing only the row to the
# parent's db while the agent stayed on the launch handle would
# recreate the cross-profile split one turn later.
parent_home = session.get("profile_home")
branch_db = None
if parent_home:
from hermes_state import SessionDB
branch_db = SessionDB(db_path=Path(parent_home) / "state.db")
home_token = (
set_hermes_home_override(parent_home) if parent_home else None
)
try:
agent = _make_agent(
tokens = _set_session_context(new_key)
try:
agent = _make_agent(
new_sid,
new_key,
session_id=new_key,
session_db=branch_db,
platform_override=source,
)
finally:
_clear_session_context(tokens)
_init_session(
new_sid,
new_key,
session_id=new_key,
platform_override=source,
agent,
list(history),
cols=session.get("cols", 80),
cwd=_session_cwd(session),
session_db=branch_db,
source=source,
profile_home=parent_home,
)
finally:
_clear_session_context(tokens)
parent_home = session.get("profile_home")
_init_session(
new_sid,
new_key,
agent,
list(history),
cols=session.get("cols", 80),
cwd=_session_cwd(session),
source=source,
profile_home=parent_home,
)
if home_token is not None:
reset_hermes_home_override(home_token)
if new_sid in _sessions:
_sessions[new_sid]["active_session_lease"] = lease
except Exception as e: