[eric] agents: prompt_context public fns lose leading-_ (convention); drop dead delegators

This commit is contained in:
ciregenz
2026-06-23 02:44:46 -07:00
parent 4fadac987a
commit 44832b2b13
8 changed files with 37 additions and 50 deletions
+4 -17
View File
@@ -82,11 +82,7 @@ from backend.apps.agents.manager.session.history_compaction import (
_estimate_post_compact_input,
_get_branch_messages,
)
from backend.apps.agents.manager.prompt.prompt_context import (
_resolve_attached_skills,
_resolve_forced_tools,
_resolve_mode,
)
from backend.apps.agents.manager.prompt.prompt_context import resolve_mode
from backend.apps.agents.manager.prompt.attachments import (
_build_dir_tree,
_build_prompt_content,
@@ -108,9 +104,6 @@ class AgentManager:
# multi-second SDK teardown the cancel handler sits behind.
self._live_partial: dict[str, dict] = {}
def _resolve_mode(self, mode_id: str) -> tuple[list[str], str | None, str | None]:
return _resolve_mode(mode_id, get_all_tool_names)
async def _build_mcp_servers(
self,
allowed_tools: list[str],
@@ -188,7 +181,7 @@ class AgentManager:
async def launch_agent(self, config: AgentConfig) -> AgentSession:
session_id = uuid4().hex
mode_tools, _, mode_folder = self._resolve_mode(config.mode)
mode_tools, _, mode_folder = resolve_mode(config.mode, get_all_tool_names)
tools = mode_tools
global_settings = load_settings()
@@ -291,12 +284,6 @@ class AgentManager:
def _build_dir_tree(self, root: str, max_depth: int = 4, prefix: str = "") -> list[str]:
return _build_dir_tree(root, max_depth, prefix)
def _resolve_forced_tools(self, forced_tools: list[str] | None) -> str:
return _resolve_forced_tools(forced_tools)
def _resolve_attached_skills(self, attached_skills: list | None) -> str:
return _resolve_attached_skills(attached_skills)
# ------------------------------------------------------------------
# Compaction & token guard (Phase 2)
#
@@ -410,7 +397,7 @@ class AgentManager:
return await tool_result_hook.post_tool_hook(hook_ctx, input_data, tool_use_id, context)
try:
_, mode_sys_prompt, _ = self._resolve_mode(session.mode)
_, mode_sys_prompt, _ = resolve_mode(session.mode, get_all_tool_names)
# Reconcile active_mcps against currently-enabled tools (Phase 3).
# If the user toggled a server off in the Tools page mid-session,
@@ -2527,7 +2514,7 @@ class AgentManager:
session_changed = True
if mode and mode != session.mode:
session.mode = mode
mode_tools, _, _ = self._resolve_mode(mode)
mode_tools, _, _ = resolve_mode(mode, get_all_tool_names)
session.allowed_tools = mode_tools
session_changed = True
if session_changed:
@@ -1,6 +1,6 @@
import os
from backend.apps.agents.manager.prompt.prompt_context import _resolve_attached_skills, _resolve_forced_tools
from backend.apps.agents.manager.prompt.prompt_context import resolve_attached_skills, resolve_forced_tools
def _build_dir_tree(root: str, max_depth: int = 4, prefix: str = "") -> list[str]:
@@ -45,8 +45,8 @@ def _build_prompt_content(prompt: str, images: list | None = None, context_paths
context_text, native_blocks, refusals = _resolve_attachments(
context_paths, api_type=api_type, model=model,
)
forced_tools_text = _resolve_forced_tools(forced_tools)
skills_text = _resolve_attached_skills(attached_skills)
forced_tools_text = resolve_forced_tools(forced_tools)
skills_text = resolve_attached_skills(attached_skills)
refusal_text = "\n\n".join(refusals)
parts = [p for p in (forced_tools_text, context_text, refusal_text, skills_text, prompt) if p]
@@ -8,7 +8,7 @@ from backend.apps.tools_lib.tools_lib import (
from backend.apps.agents.manager.prompt.tool_catalog import _get_denied_tool_names, _is_fully_denied
def _resolve_mode(mode_id: str, get_all_tool_names: Callable[[], list[str]]) -> tuple[list[str], str | None, str | None]:
def resolve_mode(mode_id: str, get_all_tool_names: Callable[[], list[str]]) -> tuple[list[str], str | None, str | None]:
"""Return (tools, system_prompt, default_folder) resolved from the mode store."""
mode_def = load_mode(mode_id)
if mode_def:
@@ -17,7 +17,7 @@ def _resolve_mode(mode_id: str, get_all_tool_names: Callable[[], list[str]]) ->
return get_all_tool_names(), None, None
def _build_connected_tools_context(allowed_tools: list[str], get_all_tool_names: Callable[[], list[str]]) -> str | None:
def build_connected_tools_context(allowed_tools: list[str], get_all_tool_names: Callable[[], list[str]]) -> str | None:
"""Build a context block describing connected MCP tools and their accounts.
Tools set to 'deny' and fully-denied servers are excluded.
@@ -127,7 +127,7 @@ def toolsearch_loop_redirect(consecutive_toolsearch: int, gated_servers: list[st
return reason
def _build_browser_context(dashboard_id: str | None, selected_browser_ids: list[str] | None = None) -> str | None:
def build_browser_context(dashboard_id: str | None, selected_browser_ids: list[str] | None = None) -> str | None:
"""Build a context block listing browser cards and delegation instructions.
Only browser cards explicitly selected by the user are included.
@@ -203,7 +203,7 @@ def _build_browser_context(dashboard_id: str | None, selected_browser_ids: list[
return "\n".join(lines)
def _build_selected_app_context(selected_app_output_ids: list[str] | None) -> str | None:
def build_selected_app_context(selected_app_output_ids: list[str] | None) -> str | None:
"""Build a context block for dashboard App cards the user selected to edit.
Resolves each Output id to its on-disk workspace so the agent edits the
@@ -262,7 +262,7 @@ def _build_selected_app_context(selected_app_output_ids: list[str] | None) -> st
)
def _build_selected_settings_context(selected_setting_ids: list[str] | None) -> str | None:
def build_selected_settings_context(selected_setting_ids: list[str] | None) -> str | None:
"""Context block when the user points the agent at specific Settings rows.
A targeting aid, NOT a gate: the settings tools (SettingsRead/SettingsWrite)
@@ -284,7 +284,7 @@ def _build_selected_settings_context(selected_setting_ids: list[str] | None) ->
)
def _build_mcp_registry_summary(allowed_tools: list[str], active_mcps: list[str], get_all_tool_names: Callable[[], list[str]]) -> str | None:
def build_mcp_registry_summary(allowed_tools: list[str], active_mcps: list[str], get_all_tool_names: Callable[[], list[str]]) -> str | None:
"""Compact registry of installed MCP servers, one line per server.
This is the visible surface that drives the activation gate: the model
@@ -411,14 +411,14 @@ AGENT_IDENTITY = (
)
def _compose_system_prompt(default_prompt: str | None, mode_prompt: str | None, session_prompt: str | None, connected_tools_ctx: str | None = None, browser_ctx: str | None = None, mcp_registry_ctx: str | None = None) -> str | None:
def compose_system_prompt(default_prompt: str | None, mode_prompt: str | None, session_prompt: str | None, connected_tools_ctx: str | None = None, browser_ctx: str | None = None, mcp_registry_ctx: str | None = None) -> str | None:
# Identity always leads so it overrides the preset's Claude Code persona, even
# when the user has no custom default/mode/session prompt of their own.
parts = [AGENT_IDENTITY] + [p for p in (default_prompt, mode_prompt, session_prompt, connected_tools_ctx, mcp_registry_ctx, browser_ctx) if p]
return "\n\n".join(parts)
def _resolve_forced_tools(forced_tools: list[str] | None) -> str:
def resolve_forced_tools(forced_tools: list[str] | None) -> str:
"""Build a context block describing explicitly requested tools."""
if not forced_tools:
return ""
@@ -458,7 +458,7 @@ def _resolve_forced_tools(forced_tools: list[str] | None) -> str:
)
def _resolve_attached_skills(attached_skills: list | None) -> str:
def resolve_attached_skills(attached_skills: list | None) -> str:
"""Build a context block injecting attached skill content into the prompt.
For a multi-file (folder) skill we inject the SKILL.md body as text AND point
@@ -12,11 +12,11 @@ from typeguard import typechecked
from backend.apps.agents.core.models import AgentSession
from backend.apps.agents.manager.prompt.tool_catalog import get_all_tool_names
from backend.apps.agents.manager.prompt.prompt_context import (
_build_browser_context as build_browser_context,
_build_mcp_registry_summary as build_mcp_registry_summary,
_build_selected_app_context as build_selected_app_context,
_build_selected_settings_context as build_selected_settings_context,
_compose_system_prompt as compose_system_prompt,
build_browser_context,
build_mcp_registry_summary,
build_selected_app_context,
build_selected_settings_context,
compose_system_prompt,
)
@@ -13,7 +13,7 @@ from backend.apps.agents.manager.prompt import prompt_context as pc
def _fake_dashboard(monkeypatch):
# _build_browser_context loads the dashboard; give it a minimal one so it
# build_browser_context loads the dashboard; give it a minimal one so it
# gets past the load and emits the static delegation guidance.
import backend.apps.dashboards.dashboards as dash
@@ -25,7 +25,7 @@ def _fake_dashboard(monkeypatch):
def test_orchestrator_routes_same_flow_batches_to_one_agent(monkeypatch):
_fake_dashboard(monkeypatch)
ctx = pc._build_browser_context("dash-1", selected_browser_ids=[])
ctx = pc.build_browser_context("dash-1", selected_browser_ids=[])
assert ctx is not None
# the key guidance: one agent + the whole list, not one agent per item
assert "Give ONE agent the whole list" in ctx
@@ -35,4 +35,4 @@ def test_orchestrator_routes_same_flow_batches_to_one_agent(monkeypatch):
def test_browser_context_is_none_without_a_dashboard():
assert pc._build_browser_context(None) is None
assert pc.build_browser_context(None) is None
@@ -42,10 +42,10 @@ def test_message_endpoint_threads_selected_setting_ids(client, monkeypatch):
def test_selected_settings_context_block_targets_the_fields():
from backend.apps.agents.manager.prompt.prompt_context import _build_selected_settings_context
assert _build_selected_settings_context(None) is None
assert _build_selected_settings_context([]) is None
block = _build_selected_settings_context(["theme", "default_model"])
from backend.apps.agents.manager.prompt.prompt_context import build_selected_settings_context
assert build_selected_settings_context(None) is None
assert build_selected_settings_context([]) is None
block = build_selected_settings_context(["theme", "default_model"])
assert "theme" in block and "default_model" in block
# It tells the agent to use the always-on settings tools on exactly these fields.
assert "SettingsRead" in block and "SettingsWrite" in block
@@ -142,13 +142,13 @@ def test_install_dedups_instead_of_clobbering_existing_skill(skills_dir):
def test_confirm_install_writes_folder_lists_and_injects(skills_dir, monkeypatch):
"""End-to-end install->usable: confirm=true through the real /install endpoint
writes the folder skill, it shows up in /api/skills/list with supporting
files, and _resolve_attached_skills injects it with the folder path so an
files, and resolve_attached_skills injects it with the folder path so an
agent can read its scripts. (resolve is mocked to skip the network; the live
GitHub resolve is proven separately.)"""
import secrets as _secrets
from fastapi.testclient import TestClient
from backend.main import app
from backend.apps.agents.manager.prompt.prompt_context import _resolve_attached_skills
from backend.apps.agents.manager.prompt.prompt_context import resolve_attached_skills
import backend.auth as auth_mod
if not auth_mod._TOKEN:
auth_mod._TOKEN = _secrets.token_urlsafe(32)
@@ -175,7 +175,7 @@ def test_confirm_install_writes_folder_lists_and_injects(skills_dir, monkeypatch
assert (skills_dir / slug / "SKILL.md").exists()
assert (skills_dir / slug / "scripts" / "extract.py").exists()
# Injectable: the agent gets the body AND a pointer to the folder for on-demand reads.
block = _resolve_attached_skills([{"id": slug, "name": "PDF Tools", "content": "# PDF Tools\nRun scripts/extract.py to pull text."}])
block = resolve_attached_skills([{"id": slug, "name": "PDF Tools", "content": "# PDF Tools\nRun scripts/extract.py to pull text."}])
assert "[Using skill: PDF Tools]" in block
assert str(skills_dir / slug) in block
+6 -6
View File
@@ -14,7 +14,7 @@ import json
import pytest
import backend.apps.skills.skills as skills_mod
from backend.apps.agents.manager.prompt.prompt_context import _resolve_attached_skills
from backend.apps.agents.manager.prompt.prompt_context import resolve_attached_skills
@pytest.fixture
@@ -116,7 +116,7 @@ def test_injection_points_at_folder_for_supporting_files(skills_dir):
_write(str(base / "SKILL.md"), "use the template")
_write(str(base / "template.html"), "<html></html>")
block = _resolve_attached_skills([{"id": "withfiles", "name": "WithFiles", "content": "use the template"}])
block = resolve_attached_skills([{"id": "withfiles", "name": "WithFiles", "content": "use the template"}])
assert "[Using skill: WithFiles]" in block
assert str(base) in block
assert "Read" in block # tells the agent to read supporting files
@@ -129,17 +129,17 @@ def test_skill_injection_is_provider_agnostic_by_construction(skills_dir):
builtin set every provider gets. So a non-Claude agent receives byte-identical
skill text and the same file-reading tools."""
import inspect
from backend.apps.agents.manager.prompt.prompt_context import _resolve_attached_skills
from backend.apps.agents.manager.prompt.prompt_context import resolve_attached_skills
from backend.apps.agents.manager.prompt.tool_catalog import FULL_TOOLS
# 1. No provider/api parameter -> the injected text cannot branch on the model.
assert set(inspect.signature(_resolve_attached_skills).parameters) == {"attached_skills"}
assert set(inspect.signature(resolve_attached_skills).parameters) == {"attached_skills"}
# 2. A folder skill yields the body + a pointer to its folder via Read/Glob/Bash.
base = skills_dir / "vid"
_write(str(base / "SKILL.md"), "render it")
_write(str(base / "helper.py"), "x")
block = _resolve_attached_skills([{"id": "vid", "name": "Vid", "content": "render it"}])
block = resolve_attached_skills([{"id": "vid", "name": "Vid", "content": "render it"}])
assert "[Using skill: Vid]" in block
assert str(base) in block and "Read" in block
@@ -149,7 +149,7 @@ def test_skill_injection_is_provider_agnostic_by_construction(skills_dir):
def test_injection_no_folder_note_for_flat_skill(skills_dir):
_write(str(skills_dir / "plain.md"), "plain content")
block = _resolve_attached_skills([{"id": "plain", "name": "Plain", "content": "plain content"}])
block = resolve_attached_skills([{"id": "plain", "name": "Plain", "content": "plain content"}])
assert "[Using skill: Plain]" in block
assert "supporting files" not in block.lower()