@@ -157,7 +157,23 @@ def ensure_turbo_memory_installed(quiet: bool = False) -> Optional[str]:
157157 existing = resolve_binary ()
158158 if existing :
159159 # Best-effort upgrade; never fail the caller on a network hiccup.
160- _run ([uv , "tool" , "upgrade" , BINARY ], _UPGRADE_TIMEOUT )
160+ #
161+ # rev-pin trap: if a PRIOR install pinned the receipt to a concrete git
162+ # rev (observed on prod: rev=v0.17.0), `uv tool upgrade` re-resolves to
163+ # that SAME rev and never jumps to a newer commit — the install stays
164+ # silently stale. REPO_SPEC is intentionally unpinned (no @rev) so a
165+ # reinstall floats to the branch HEAD. We try the cheap upgrade first
166+ # (fast on the common, already-latest case) and only fall back to a
167+ # `--reinstall` against the unpinned spec when the upgrade reported no
168+ # change ("Nothing to upgrade" / non-zero) — that re-pins the receipt to
169+ # the unpinned spec and breaks the rev-pin trap without slowing the
170+ # normal path.
171+ up = _run ([uv , "tool" , "upgrade" , BINARY ], _UPGRADE_TIMEOUT )
172+ out = (up .stdout or "" ) + (up .stderr or "" )
173+ upgrade_had_effect = up .returncode == 0 and "Nothing to upgrade" not in out
174+ if not upgrade_had_effect :
175+ # Re-resolve from the unpinned REPO_SPEC to escape a rev-pinned receipt.
176+ _run ([uv , "tool" , "install" , "--reinstall" , REPO_SPEC ], _INSTALL_TIMEOUT )
161177 return resolve_binary () or existing
162178
163179 _emit (quiet , "🧠 Installing Turbo-Quant Memory MCP (one-time, may take a minute)…" )
@@ -177,10 +193,21 @@ def ensure_turbo_memory_installed(quiet: bool = False) -> Optional[str]:
177193# ---------------------------------------------------------------------------
178194
179195def _build_entry (tqm_path : str ) -> dict :
196+ # Pin TQMEMORY_PROJECT_ROOT to a STABLE root (HERMES_HOME, fallback ~/.hermes)
197+ # so turbo_quant_memory derives a single, cwd-independent project_id. Without
198+ # it the project_id tracks the process cwd and memory fragments into multiple
199+ # buckets (observed on prod: /root vs /root/.hermes).
200+ hermes_home = os .path .expanduser (os .environ .get ("HERMES_HOME" , "~/.hermes" ))
201+ env = dict (_SERVER_ENV )
202+ env .setdefault ("TQMEMORY_PROJECT_ROOT" , hermes_home )
180203 return {
181204 "command" : tqm_path ,
182205 "args" : ["serve" ],
183- "env" : dict (_SERVER_ENV ),
206+ "env" : env ,
207+ # First semantic_search loads a ~600MB embedding model; re-syncs can be
208+ # slow. Give this server a generous per-call timeout (read per-server by
209+ # tools/mcp_tool.py) without touching the global MCP default.
210+ "timeout" : 600 ,
184211 "enabled" : True ,
185212 }
186213
@@ -219,15 +246,21 @@ def _register_in_config_file(config_path: Path, tqm_path: str) -> bool:
219246 if isinstance (existing , dict ):
220247 # Already registered. Leave a user-disabled entry (enabled: false)
221248 # untouched so we respect intent. Otherwise repair anything that drifted:
222- # a stale absolute command path OR a missing migrate-on-startup env.
249+ # a stale absolute command path, a missing migrate-on-startup env, a
250+ # missing stable project root, or a missing per-server timeout. Repairing
251+ # the project root on EXISTING installs (not just fresh ones) is what lets
252+ # `hermes update` heal client installs whose memory fragmented by cwd.
223253 if existing .get ("enabled" ) is False :
224254 return False
255+ canonical = _build_entry (tqm_path )
225256 env = existing .get ("env" )
226257 already_correct = (
227258 existing .get ("command" ) == tqm_path
228259 and existing .get ("args" ) == ["serve" ]
229260 and isinstance (env , dict )
230261 and env .get ("TQMEMORY_MIGRATE_ON_STARTUP" ) == "1"
262+ and env .get ("TQMEMORY_PROJECT_ROOT" ) == canonical ["env" ]["TQMEMORY_PROJECT_ROOT" ]
263+ and existing .get ("timeout" ) == canonical ["timeout" ]
231264 )
232265 if already_correct :
233266 return False
@@ -236,7 +269,11 @@ def _register_in_config_file(config_path: Path, tqm_path: str) -> bool:
236269 if not isinstance (env , dict ):
237270 env = {}
238271 env .setdefault ("TQMEMORY_MIGRATE_ON_STARTUP" , "1" )
272+ # Backfill a stable project root so project_id no longer tracks cwd.
273+ # setdefault: never clobber an operator-chosen TQMEMORY_PROJECT_ROOT.
274+ env .setdefault ("TQMEMORY_PROJECT_ROOT" , canonical ["env" ]["TQMEMORY_PROJECT_ROOT" ])
239275 existing ["env" ] = env
276+ existing .setdefault ("timeout" , canonical ["timeout" ])
240277 existing ["enabled" ] = True
241278 else :
242279 servers [SERVER_NAME ] = _build_entry (tqm_path )
0 commit comments