From 206f595f6639ff315c09dea6073ca6a3a7408e49 Mon Sep 17 00:00:00 2001 From: RyanRana <39924576+RyanRana@users.noreply.github.com> Date: Mon, 18 May 2026 20:56:38 -0700 Subject: [PATCH] perf(prompt): cache kanban worker guidance at session init MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Salvages #24402 by @RyanRana. The KANBAN_GUIDANCE block (~835 tokens) is session-static — the dispatcher decides at spawn time whether the process is a kanban worker via the kanban_show tool's check_fn (gated on HERMES_KANBAN_TASK env var). Re-checking 'kanban_show' in valid_tool_names and re-loading the reference on every system-prompt rebuild (init + each context compression) is wasted work. Caches the resolved string on agent._kanban_worker_guidance once in agent_init and consumes it in system_prompt.build_system_prompt(), with a getattr fallback for code paths that bypass agent_init. --- agent/agent_init.py | 14 ++++++++++++-- agent/system_prompt.py | 8 ++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/agent/agent_init.py b/agent/agent_init.py index 9b89028e3fa..a5d27c0b73d 100644 --- a/agent/agent_init.py +++ b/agent/agent_init.py @@ -828,7 +828,6 @@ def init_agent( tool_names = sorted(agent.valid_tool_names) if not agent.quiet_mode: print(f"🛠️ Loaded {len(agent.tools)} tools: {', '.join(tool_names)}") - # Show filtering info if applied if enabled_toolsets: print(f" ✅ Enabled toolsets: {', '.join(enabled_toolsets)}") @@ -836,7 +835,18 @@ def init_agent( print(f" ❌ Disabled toolsets: {', '.join(disabled_toolsets)}") elif not agent.quiet_mode: print("🛠️ No tools loaded (all tools filtered out or unavailable)") - + + # Kanban worker/orchestrator lifecycle guidance is session-static: + # the dispatcher decides at spawn time whether this process is a kanban + # worker (kanban_show tool is present iff HERMES_KANBAN_TASK is set). + # Resolving the ~835-token block once here avoids re-running the + # membership test + reference on every system-prompt rebuild + # (init + each context compression). + from agent.prompt_builder import KANBAN_GUIDANCE + agent._kanban_worker_guidance = ( + KANBAN_GUIDANCE if "kanban_show" in agent.valid_tool_names else "" + ) + # Check tool requirements if agent.tools and not agent.quiet_mode: requirements = _ra().check_toolset_requirements() diff --git a/agent/system_prompt.py b/agent/system_prompt.py index a9815a2f2f4..bc29c9ef89a 100644 --- a/agent/system_prompt.py +++ b/agent/system_prompt.py @@ -111,8 +111,12 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) # Kanban worker/orchestrator lifecycle — only present when the # dispatcher spawned this process (kanban_show check_fn gates on # HERMES_KANBAN_TASK env var). Normal chat sessions never see - # this block. - if "kanban_show" in agent.valid_tool_names: + # this block. Resolved once at __init__ (see _kanban_worker_guidance). + _kanban_guidance = getattr(agent, "_kanban_worker_guidance", None) + if _kanban_guidance: + tool_guidance.append(_kanban_guidance) + elif _kanban_guidance is None and "kanban_show" in agent.valid_tool_names: + # Fallback for code paths that bypass agent_init (rare). tool_guidance.append(KANBAN_GUIDANCE) if tool_guidance: stable_parts.append(" ".join(tool_guidance))