From cedc933c1ffe78f3cf2a64379b37b26d4f9a6dfd Mon Sep 17 00:00:00 2001 From: Brooklyn Nicholson Date: Sun, 9 Aug 2026 03:42:00 -0500 Subject: [PATCH] fix(agent): stop cron and subagent runs auto-titling their sessions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The turn prologue titles every session, and it is shared by every agent — including the ones no person is reading. A cron job already names its own session after the job in its finally block, so the titler spent a side-LLM call per fire to write the delivery scaffolding over it for the length of the run. A delegated child's session is hidden from every picker, so a batch at max_concurrent_children paid N title calls for N names nobody opens. Both are the same class of run that already sets skip_memory to stay off the auxiliary path, so keep the titler off it too. --- agent/turn_context.py | 24 +++++++++++++++++++--- tests/agent/test_turn_context.py | 35 ++++++++++++++++++++++++++++++-- 2 files changed, 54 insertions(+), 5 deletions(-) diff --git a/agent/turn_context.py b/agent/turn_context.py index ab514c14aa..7c36a923aa 100644 --- a/agent/turn_context.py +++ b/agent/turn_context.py @@ -170,18 +170,36 @@ def append_notes_to_multimodal_content(content: Any, notes: str) -> bool: return False +# Surfaces whose sessions must not be auto-titled. The prologue is shared by +# EVERY agent, not only the ones a human is watching, so membership here is what +# keeps the titler off machine-driven runs: +# +# - cron — the scheduler names its own session after the job in its `finally` +# block, and the opener is the cron delivery hint, not a user's request. +# Titling it writes that scaffolding as the visible name for the whole run and +# bills a side-LLM call per fire, against the same job that sets +# `skip_memory` / `skip_background_review` to avoid exactly that. +# - subagent — a delegated child's session is hidden from every picker, so its +# title is never read. A batch at `max_concurrent_children` would pay N title +# calls for N names nobody sees. +_UNTITLED_PLATFORMS = frozenset({"cron", "subagent"}) + + def _maybe_title_session_at_turn_start(agent: Any, messages: List[Any]) -> None: """Kick off auto-titling for this session's first user message. - Called from the turn prologue, so every surface (CLI, gateway, TUI/desktop, - ACP) gets identical behavior without each one re-implementing the call. - Fully defensive: titling is cosmetic and must never break a turn. + Called from the turn prologue, so every surface a human reads (CLI, gateway, + TUI/desktop, ACP) gets identical behavior without each one re-implementing + the call. Fully defensive: titling is cosmetic and must never break a turn. """ session_db = getattr(agent, "_session_db", 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: + return + try: from agent.message_content import flatten_message_text from agent.title_generator import maybe_auto_title diff --git a/tests/agent/test_turn_context.py b/tests/agent/test_turn_context.py index cf5ff86abf..71f7942255 100644 --- a/tests/agent/test_turn_context.py +++ b/tests/agent/test_turn_context.py @@ -365,13 +365,44 @@ def test_between_turns_refresh_adds_late_tool_when_servers_registered(): assert any(t["function"]["name"] == "mcp_x_tool" for t in agent.tools) +class _TitlingAgent: + """Only what ``_maybe_title_session_at_turn_start`` reads off an agent.""" + + def __init__(self, platform): + self.platform = platform + self.session_id = "sess-1" + self.model = "test/model" + self.provider = "openrouter" + self.base_url = "https://openrouter.ai/api/v1" + self.api_key = "sk-x" + self.api_mode = "chat_completions" + self._session_db = MagicMock() + self._session_db_created = True +def _title_turn(platform, message="Fix the login button"): + """Run the prologue's titling step and return the maybe_auto_title mock.""" + from agent import turn_context + + with patch("agent.title_generator.maybe_auto_title") as titler: + turn_context._maybe_title_session_at_turn_start( + _TitlingAgent(platform), + [{"role": "user", "content": message}], + ) + return titler +@pytest.mark.parametrize("platform", ["cli", "telegram", "desktop", "acp", None]) +def test_prologue_titles_the_surfaces_a_person_reads(platform): + assert _title_turn(platform).called +@pytest.mark.parametrize("platform", ["cron", "CRON", "subagent"]) +def test_prologue_does_not_title_machine_driven_runs(platform): + """Cron names its own session after the job, and nobody opens a subagent's. - - + Both would otherwise pay a side-LLM call per run for a name that is either + overwritten or never read. + """ + assert not _title_turn(platform).called