diff --git a/CHANGELOG.md b/CHANGELOG.md index 2861231d..755801f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Status of the `main` branch. Changes prior to the next official version change w failed `--project-from-cwd` auto-detection (#1773). - ProjectServer: Configure trusted hosts (local hosts only) when listening on localhost - SerenaDashboardTrayManager: Configure trusted hosts (local hosts only) + - Enclose sub-prompts in XML-like tags to make scopes explicit + - Allow initial project prompts and project-specific newly activated modes to use templating * CLI: - Fix: `start-mcp-server` help text for `--project-from-cwd` falsely promised a fallback to the CWD, which was diff --git a/src/serena/agent.py b/src/serena/agent.py index 2b70389a..b9649fcf 100644 --- a/src/serena/agent.py +++ b/src/serena/agent.py @@ -956,14 +956,24 @@ class SerenaAgent: result[tool_class.get_name_from_cls()] = new_tool_class.get_name_from_cls() return result - def _format_prompt(self, prompt_template: str) -> str: + def _format_prompt_tag(self, text: str, tag: str, tag_name_attr: str | None = None) -> str: + open_tag = f"<{tag}" + (f' name="{tag_name_attr}"' if tag_name_attr is not None else "") + ">" + close_tag = f"" + return f"{open_tag}\n{text.strip()}\n{close_tag}" + + def _format_prompt(self, prompt_template: str, tag: str | None = None, tag_name_attr: str | None = None) -> str: template = JinjaTemplate(prompt_template) - return template.render( + text = template.render( available_tools=self._exposed_tools.tool_names, available_markers=self._exposed_tools.tool_marker_names, tool_names=self._prompt_tool_names_mapping, ) + if tag is not None: + text = self._format_prompt_tag(text, tag=tag, tag_name_attr=tag_name_attr) + + return text + def create_connection_prompt(self) -> str: """ Returns the bootstrap prompt to be sent at MCP connection time. @@ -997,8 +1007,8 @@ class SerenaAgent: self._project_prompt_status.mark_mode_prompts_as_provided(session_id) system_prompt = self.prompt_factory.create_system_prompt( - context_system_prompt=self._format_prompt(self._context.prompt), - mode_system_prompts=[self._format_prompt(mode.prompt) for mode in relevant_modes], + context_system_prompt=self._format_prompt(self._context.prompt, tag="context"), + mode_system_prompts=[self._format_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) for mode in relevant_modes], available_tools=available_tools.tool_names, available_markers=available_markers, global_memories_list=global_memories_str, @@ -1007,11 +1017,11 @@ class SerenaAgent: # provide the project activation message if it hasn't yet been provided if self._active_project is not None and not self._project_prompt_status.is_project_activation_message_already_provided(session_id): - system_prompt += "\n\n" + self.get_project_activation_message(session_id) + system_prompt += "\n\n" + self._format_prompt_tag(self.get_project_activation_message(session_id), tag="active-project") elif self._project_activation_error: system_prompt += f"\n\nNo project is active ({self._project_activation_error})." - return system_prompt + return self._format_prompt_tag(system_prompt, tag="serena") def get_project_activation_message(self, session_id: str) -> str: """ @@ -1031,13 +1041,13 @@ class SerenaAgent: # provide basic project information (name, location, languages, encoding) if proj.is_newly_created: - msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}. " + msg = f"Created and activated a new project with name '{proj.project_name}' at {proj.project_root}.\n" else: - msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated." + msg = f"The project with name '{proj.project_name}' at {proj.project_root} is activated.\n" if self._language_backend == LanguageBackend.LSP: - languages_str = ", ".join([lang.value for lang in proj.project_config.language_servers]) - msg += f"\nProgramming languages: {languages_str}." - msg += f"File encoding: {proj.project_config.encoding}." + language_servers_str = ", ".join([ls.value for ls in proj.project_config.language_servers]) + msg += f"Active language servers: {language_servers_str}.\n" + msg += f"File encoding: {proj.project_config.encoding}.\n" # add list of memories (if memories are enabled) include_memories = self._active_tools.contains_tool_class(ReadMemoryTool) @@ -1045,23 +1055,22 @@ class SerenaAgent: project_memories = proj.memory_manager.list_project_memories() if project_memories: msg += ( - f"\n{json.dumps(project_memories.to_dict())}\n" - + "Use the `read_memory` tool to read these memories later if they are relevant to the task." + f"{json.dumps(project_memories.to_dict())}\n" + + f"Use the `{ReadMemoryTool.get_name_from_cls()}` tool to read these memories later if they are relevant to the task.\n" ) elif self._active_tools.contains_tool_class(OnboardingTool): - msg += "Onboarding has not been performed yet, you should call Serena's `onboarding` tool now to set up project memories." + msg += f"Onboarding has not been performed yet. Ask the user whether to perform onboarding via the `{OnboardingTool.get_name_from_cls()}` tool.\n" # add prompts for modes that were dynamically activated by the project modes_with_prompts = self._project_prompt_status.get_modes_with_prompts_to_be_provided_for_project_activation(session_id) if modes_with_prompts: - msg += "\nNewly applicable mode instructions:" for mode in modes_with_prompts: - msg += f"\n{mode.prompt}" + msg += self._format_prompt(mode.prompt, tag="mode", tag_name_attr=mode.name) + "\n" self._project_prompt_status.mark_mode_prompts_as_provided(session_id) # add project-specific prompt if proj.project_config.initial_prompt: - msg += f"\nProject-specific instructions:\n {proj.project_config.initial_prompt}" + msg += "\n" + self._format_prompt(proj.project_config.initial_prompt, tag="project-instructions") self._project_prompt_status.mark_project_activation_message_as_provided(session_id) diff --git a/src/serena/resources/config/prompt_templates/system_prompt.yml b/src/serena/resources/config/prompt_templates/system_prompt.yml index 78a7ad18..99604bf1 100644 --- a/src/serena/resources/config/prompt_templates/system_prompt.yml +++ b/src/serena/resources/config/prompt_templates/system_prompt.yml @@ -49,10 +49,8 @@ prompts: The context and modes of operation are described below. These determine how to interact with your user and which kinds of interactions are expected of you. - Context description: {{ context_system_prompt }} - Modes descriptions: {% for prompt in mode_system_prompts %} {{ prompt }} {% endfor %}