mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(core): keep agent-tool render idempotent so no-persona sessions share the tool constant
_render_agent_tool_descriptions rebuilt self._tools and reassigned it on every session init, deep-copying task_agent even with no model aliases and no personas to inject — the single-model CLI case the docstring says is skipped. This regressed after the persona-discoverability change removed the early 'if self._registry is None: return' guard, breaking the session._tools is INTERACTIVE_TOOLS invariant (test_session_without_mcp). Gate the reassignment on whether a description actually changed: keep the original tool object when the render is a no-op, fork self._tools only when something was injected. Restores the shared-constant invariant, makes repeated renders idempotent, and preserves clear-stale-on-reload (an emptied registry still takes the changed path).
This commit is contained in:
@@ -2589,6 +2589,7 @@ class ChatSession:
|
||||
persona_base[fn["name"]] = (prop or {}).get("description", "")
|
||||
|
||||
new_tools: list[dict[str, Any]] = []
|
||||
changed = False
|
||||
for tool in self._tools:
|
||||
fn = tool.get("function") or {}
|
||||
name = fn.get("name", "")
|
||||
@@ -2621,8 +2622,23 @@ class ChatSession:
|
||||
if prop is not None:
|
||||
base = persona_base.get(name, "")
|
||||
prop["description"] = f"{base} {persona_line}".strip() if persona_line else base
|
||||
new_tools.append(new_tool)
|
||||
self._tools = new_tools
|
||||
if new_tool == tool:
|
||||
# The rendered description is identical to what this tool
|
||||
# already carries — nothing dynamic to inject (no aliases,
|
||||
# no personas). Keep the original object so a session with
|
||||
# nothing to render keeps sharing the pristine module-level
|
||||
# tool list instead of allocating a per-session copy.
|
||||
new_tools.append(tool)
|
||||
else:
|
||||
new_tools.append(new_tool)
|
||||
changed = True
|
||||
# Swap in the rebuilt list only when a description actually changed.
|
||||
# This keeps the render idempotent and allocation-free when there is
|
||||
# nothing to inject, so the shared constants (e.g. INTERACTIVE_TOOLS)
|
||||
# stay referenced by such sessions; a reload that clears a prior
|
||||
# render still takes the ``changed`` path and replaces the stale list.
|
||||
if changed:
|
||||
self._tools = new_tools
|
||||
|
||||
@staticmethod
|
||||
def _persona_property(props: dict[str, Any]) -> dict[str, Any] | None:
|
||||
|
||||
Reference in New Issue
Block a user