fix(delegate): title subagent sessions after their goal without a model call

This commit is contained in:
Hermes Agent
2026-09-24 23:48:13 -05:00
committed by brooklyn!
parent 2e43498413
commit b90b7ae7ed
3 changed files with 44 additions and 5 deletions

View File

@@ -606,6 +606,18 @@ def _persist_session_title(session_db, session_id, title, *, source, dedupe=True
return _set(deduped)
def apply_subagent_title(session_db, session_id: str, goal: str) -> Optional[str]:
"""Title a delegate run ``Subagent: <goal's first line>`` at ``derived`` authority. No model call:
runs fan out in bulk, and the prefix alone is what tells them apart from conversations wherever
``sessions.show_subagents`` lists them (#97202). Collisions get ``#N``. Never raises."""
try:
derived = derive_title(goal) if is_titleable_user_message(goal) else None
return _persist_session_title(session_db, session_id, f"Subagent: {derived}", source="derived") if derived else None
except Exception:
logger.debug("Subagent title failed for %s", session_id, exc_info=True)
return None
def apply_instant_title(
session_db, session_id: str, user_message: str, title_callback: Optional[TitleCallback] = None,
title_preview: str | None = None,

View File

@@ -157,9 +157,9 @@ def append_notes_to_multimodal_content(content: Any, notes: Optional[str]) -> bo
return False
# Surfaces whose sessions must not be auto-titled: cron names its own session and
# its opener is a delivery hint; subagent sessions are hidden from every picker.
_UNTITLED_PLATFORMS = frozenset({"cron", "subagent"})
# Cron sessions are never auto-titled: cron names its own session and its opener is a delivery hint.
# Subagent runs get a cheap ``Subagent: <goal>`` title instead of a model call (apply_subagent_title).
_UNTITLED_PLATFORMS = frozenset({"cron"})
def _maybe_title_session_at_turn_start(agent: Any, messages: List[Any]) -> None:
@@ -168,11 +168,12 @@ def _maybe_title_session_at_turn_start(agent: Any, messages: List[Any]) -> None:
session_id = getattr(agent, "session_id", None)
if not session_db or not session_id:
return
if str(getattr(agent, "platform", "") or "").lower() in _UNTITLED_PLATFORMS:
platform = str(getattr(agent, "platform", "") or "").lower()
if platform in _UNTITLED_PLATFORMS:
return
try:
from agent.message_content import flatten_message_text
from agent.title_generator import maybe_auto_title
from agent.title_generator import apply_subagent_title, maybe_auto_title
# Turn's user message as text; image-only turns yield "" and are skipped.
user_text = ""
@@ -194,6 +195,9 @@ def _maybe_title_session_at_turn_start(agent: Any, messages: List[Any]) -> None:
ensure()
if not getattr(agent, "_session_db_created", False):
return
if platform == "subagent":
apply_subagent_title(session_db, session_id, user_text)
return
# Snapshot runtime identity so the background titler can skip if the user
# switches models before it fires.
# ``session_id`` rides along so the background titler's OpenCode request carries the

View File

@@ -506,6 +506,29 @@ def test_prologue_does_not_title_machine_driven_runs(platform):
assert not _title_turn(platform).called
def test_prologue_names_a_subagent_run_after_its_goal_without_a_model_call():
"""A delegate run gets ``Subagent: <goal>`` at derived authority so it reads as machinery
wherever ``sessions.show_subagents`` lists it, instead of staying untitled (#97202)."""
from agent import turn_context
agent = _TitlingAgent("subagent")
agent._session_db.set_auto_title.return_value = True
with patch("agent.title_generator.maybe_auto_title") as titler:
turn_context._maybe_title_session_at_turn_start(
agent, [{"role": "user", "content": "Audit the billing module\n\nContext: ..."}])
assert not titler.called
agent._session_db.set_auto_title.assert_called_once_with(
"sess-1", "Subagent: Audit the billing module", source="derived")
def test_prologue_leaves_cron_runs_untitled():
agent = _TitlingAgent("cron")
from agent import turn_context
turn_context._maybe_title_session_at_turn_start(agent, [{"role": "user", "content": "Run the job"}])
assert not agent._session_db.set_auto_title.called
def test_prologue_forwards_the_submit_title_preview_to_the_titler():
"""A paste-shrunk ``display_metadata.title_preview`` from prompt.submit is the text the
titler should read, not the full pasted body."""